Compare commits

...

216 Commits

Author SHA1 Message Date
panni f337b53ae3 submod: HI: remove music
submod: common: be less aggressive about music symbols
submod: HI: be less aggressive about brackets
submod: HI: be less aggressive about MAN
2019-05-18 06:23:04 +02:00
panni aea6050d71 subtitle: try decoding with utf-16 by default as well 2019-05-17 23:45:06 +02:00
panni 13d5e0761e providers: subscene: fix endpoint once again 2019-05-13 16:14:26 +02:00
panni ce28d0284c back from dev 2019-05-12 06:17:08 +02:00
panni 1a0bb9c3e4 release 2.6.5.3074 2019-05-12 06:05:16 +02:00
panni d0c71b4b67 bump dev 2019-05-12 05:12:58 +02:00
panni b3f062956d core: re-fix ass/ssa tags in srt in pysubs2 0.2.3 2019-05-12 05:12:34 +02:00
panni 1a853a780c core: update pysubs2 to 0.2.3 2019-05-12 05:01:38 +02:00
panni 5c47ddeb2d core: update chinese encodings; #646 2019-05-12 04:49:30 +02:00
panni b51deb5d01 core: subliminal: don't replace \r with \n by default; fixes utf-16 character transformation issues; fixes #646 2019-05-12 04:48:23 +02:00
panni cbf5ea69be core: cf: update cloudscraper to 1.1.9; fix keyerror 2019-05-08 15:57:33 +02:00
panni e139ffefe6 bump dev 2019-05-08 04:18:25 +02:00
panni dc0a8deb40 core: cf: testing
providers: subscene: testing
2019-05-08 04:14:04 +02:00
panni 97e93cd10a core: cf: update js2py; update cloudscraper to 1.1.5; 2019-05-08 01:31:21 +02:00
panni 03c934cf21 back to dev 2019-05-01 15:39:23 +02:00
panni 92d0d70258 Release 2.6.5.3062 2019-05-01 15:32:36 +02:00
panni d44298993c Release 2.6.5.3055 2019-05-01 15:32:19 +02:00
panni 12300d4115 Merge branch 'develop-2.6' 2019-05-01 15:29:42 +02:00
pannal b4f08f61a6 Update README.md 2019-05-01 06:00:01 +02:00
pannal 861a25be41 Update README.md 2019-05-01 05:59:21 +02:00
pannal 3e175109a6 Merge pull request #641 from fossabot/master
Add license scan report and status
2019-05-01 05:48:14 +02:00
fossabot fb2210f2fd Add license scan report and status
Signed-off-by: fossabot <badges@fossa.io>
2019-04-30 20:44:05 -07:00
panni e928918201 add cloudscaper LICENSE 2019-05-01 05:13:13 +02:00
panni df607e5772 bump dev 2019-05-01 04:49:30 +02:00
panni a7cc470645 core: log cf domain 2019-05-01 04:48:48 +02:00
panni 4e6421b928 core: dns: set env var empty if not configured 2019-05-01 04:36:03 +02:00
panni df48e8fccd providers: subscene: remove obsolete imports 2019-05-01 04:27:11 +02:00
panni 58111bf204 core: remove old cfscrape implementation 2019-05-01 04:25:04 +02:00
panni 8c02e75fed providers: titlovi: match cfsrc for src 2019-05-01 04:24:31 +02:00
panni 6f3f1cb4b5 core: cf: harden. 2019-05-01 04:24:09 +02:00
panni dd27997deb core: cf: add cloudscaper 1.1.1@496900e instead of cfscrape 2019-05-01 03:12:01 +02:00
panni a1f70d1d4d core: add ENV:dns_resolvers_timeout 2019-05-01 02:39:18 +02:00
panni 7da0bac643 skip warning 2019-05-01 02:33:46 +02:00
panni b3ab2a451c core: http: don't query DNS with IPs. thanks @fgump 2019-05-01 02:27:30 +02:00
panni 850f836ebd back to dev 2019-04-28 05:27:26 +02:00
panni d9fa9d03da back to dev 2019-04-28 05:22:24 +02:00
pannal 76c20dc3d7 Update README.md 2019-04-28 05:21:35 +02:00
panni 4568e222d1 release 2.6.5.3041 2019-04-28 05:11:45 +02:00
panni 344025226a add missing changelog entry 2019-04-28 05:11:09 +02:00
panni f546fcffce release 2.6.5.3039 2019-04-28 05:08:00 +02:00
panni 068c2d4d00 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	Contents/Info.plist
2019-04-28 05:04:51 +02:00
panni ccf5a902e5 core: cf: only store cookie if it had a value 2019-04-28 05:03:04 +02:00
panni 8c72cf9057 bump dev 2019-04-28 04:45:17 +02:00
panni 1ce14aa231 core: http: remove debug 2019-04-28 04:44:27 +02:00
panni 643485b879 core: cf: optimize
providers: titlovi: optimize cf/captcha handling
2019-04-28 04:43:03 +02:00
pannal 5b3d9f26be Update README.md 2019-04-28 03:47:55 +02:00
panni 70674fbce7 i128n: remove obsolete trans 2019-04-28 03:30:35 +02:00
panni f48c0799c0 i128n: remove obsolete trans 2019-04-28 03:28:44 +02:00
pannal 3bc646187f Update de.json (POEditor.com) 2019-04-28 03:21:46 +02:00
pannal b692ebde6f Update de.json (POEditor.com) 2019-04-28 03:20:52 +02:00
panni d2a665624a core/config: add setting for one existing language to be enough, fixes #491 2019-04-28 03:10:33 +02:00
panni 10c8b8ceff core: dns: be a tad smarter 2019-04-27 06:43:46 +02:00
panni 92edfc7312 bump dev 2019-04-27 06:37:43 +02:00
panni eeaeb80f0f core/compat: dns: support nameservers via ENV[dns_resolvers]; don't fall back to default DNS when configured custom DNS failed 2019-04-27 06:37:09 +02:00
panni 6204572ddc core: only reference guessed title if there actually is one 2019-04-26 15:32:59 +02:00
pannal 14f2f45f20 Update README.md 2019-04-22 05:37:47 +02:00
pannal 8ac6c9d7a7 Update README.md 2019-04-22 05:31:29 +02:00
pannal 237a47b8ed Update Info.plist 2019-04-21 03:48:37 +02:00
panni 96bdf606e2 back to dev 2019-04-21 03:46:25 +02:00
panni 5cc4dcf10b update readme 2019-04-21 03:45:38 +02:00
pannal a0a3c39606 Update README.md 2019-04-21 03:44:58 +02:00
panni b4cc35b109 release 2.6.5.3017 2019-04-21 03:44:39 +02:00
panni 72eeb7eb35 bump year 2019-04-21 03:40:06 +02:00
panni 44f97411a8 Merge branch 'develop-2.6' 2019-04-21 03:39:48 +02:00
panni ce2296c95d core: guess_matches: handle multiple title matches; fixes bazarr#403 2019-04-21 03:28:52 +02:00
panni 3fe0500746 providers: opensubtitles: catch specific exceptions when testing token 2019-04-21 03:03:27 +02:00
pannal a8daaa787a Update README.md 2019-04-20 19:13:54 +02:00
pannal ae9aef9899 Update README.md 2019-04-20 19:09:54 +02:00
panni ad9be91f45 core: cfscrape: select user agent regardless 2019-04-20 17:52:40 +02:00
panni 216512788c core: add cfscrape to log 2019-04-20 17:19:17 +02:00
panni 2483a9c901 bump dev 2019-04-20 17:15:41 +02:00
panni 9147ed90b7 core: cf: update cfscrape to use proper user agents and headers; support brotli (if brotli is installed); support captcha solving in case of bad ip reputation for cf 2019-04-20 17:15:02 +02:00
panni a4ce8c8c52 Merge remote-tracking branch 'origin/develop-2.6' into develop-2.6 2019-04-20 16:41:12 +02:00
panni dc83d36193 core: update enum to 1.1.6; urllib3 to 1.24.2 2019-04-20 15:44:54 +02:00
pannal 7dbc466a58 Merge pull request #636 from robinwestra/master
Allow matching either series name or imdb_id
2019-04-20 15:39:49 +02:00
Robin Westra 5bb36bc87c Allow matching either series name or imdb_id 2019-04-20 15:24:29 +02:00
panni 7301cd259b core: update requests to 2.21.0; six to 1.12.0 2019-04-20 04:20:07 +02:00
panni 5ba5a9dfc4 core: http: separate CFSession and RetryingSession again
providers: subscene/titlovi: use RetryingCFSession
proviers: titlovi: drop explicit random user agent; drop explicit referer
2019-04-19 04:30:42 +02:00
panni 66e7c60767 core: cf: move get_live_tokens to CFSession.get_cf_live_tokens 2019-04-19 03:19:58 +02:00
panni 22dd7ef093 Revert: providers: subscene: cf: preserve headers 2019-04-19 03:13:48 +02:00
panni e03bb4f280 release 2.6.5.2997 2019-04-18 16:54:03 +02:00
panni 4d15283473 bump dev 2019-04-18 16:53:33 +02:00
panni b1dfbffc4f core: add CF debugging environment variable 2019-04-18 16:52:29 +02:00
panni a8de8dcc48 bump dev 2019-04-18 16:51:00 +02:00
panni e7cdbbcacb providers: subscene: cf: preserve headers 2019-04-18 16:48:43 +02:00
panni 060ba2a5be back to dev 2019-04-16 18:15:03 +02:00
panni 72a21e1ef4 back from dev 2019-04-16 17:52:04 +02:00
panni cbfb498b0f Merge remote-tracking branch 'origin/master' 2019-04-16 17:50:56 +02:00
panni 0ec11c532e release 2.6.5.2989 2019-04-13 16:23:33 +02:00
panni 65201749f7 providers: titlovi: might work without captcha, make it optional 2019-04-13 16:12:11 +02:00
panni 8c569183be bump dev 2019-04-13 03:45:22 +02:00
panni c3665f04d6 core: extract embedded: fix encoding for mswindows 2019-04-13 02:31:59 +02:00
panni 2406e0ad49 core: extract embedded: use fs encoding for filenames 2019-04-12 14:53:43 +02:00
panni a9490b0838 providers: subscene: relieve a bit of stress on the provider by not querying releases anymore 2019-04-11 04:59:49 +02:00
panni 77e1c69f6b providers: addic7ed: revert 6338757a9b46d6cba45299057de3099a8d61a5ff; temporarily remove _search_show_id 2019-04-11 04:02:48 +02:00
panni f2d8139bef bump dev 2019-04-11 03:54:55 +02:00
panni 92594dba1d core: http: clear up classes; reorder the MRO
refiners: tvdb/omdb: properly implement timeouts
2019-04-11 03:53:53 +02:00
panni 9a28ea7672 bump dev 2019-04-11 02:30:35 +02:00
panni b87c6c24d8 providers: assrt: support undefined Chinese as Simplified (chs/zho-Hans) 2019-04-11 02:26:29 +02:00
panni 0c166ff36a add pyjsparser==2.2.0; tzlocal; js2py==7a3a1ff; cfscrape==83ebd10; requests-toolbelt==bc1b273; remove old cfscrape
core: update cf stuff
2019-04-11 01:57:27 +02:00
panni b7c9530ac0 bump dev 2019-04-07 06:55:13 +02:00
panni e20f102cb9 core: pitchers: add current user agent to debug log
providers: titlovi: generally load previous verification(s)
providers: addic7ed: close session on terminate
2019-04-07 06:54:42 +02:00
panni 7856cc5bb3 providers: titlovi: re-enable titlovi 2019-04-07 06:40:48 +02:00
panni 663fdf6e2f bump dev 2019-04-07 06:21:04 +02:00
panni 1867aed769 core: increase cache time 2019-04-07 06:12:31 +02:00
panni 72aa6150b5 prefs: anticaptcha: remove proxy option (for now)
core: pitchers: finalize
providrs: titlovi: implement anticaptcha
2019-04-07 06:06:30 +02:00
panni 247ae71777 wip 2019-04-06 05:07:06 +02:00
panni bbf5d6712c providers: addic7ed: fix forced triple loop 2019-04-05 23:57:13 +02:00
panni d415f6a9f2 core: pitchers: add DBCProxyLessPitcher
providers: addic7ed: fixes
2019-04-05 22:24:53 +02:00
panni 1912881d05 core: add subliminal_patch.pitcher
providers: addic7ed: slim down solving
2019-04-05 16:28:08 +02:00
panni a85d9e5442 refiners: omdb: fix imdb ids with spaces 2019-04-05 05:42:14 +02:00
panni fe2f5b2d8f providers: addic7ed: clarify log 2019-04-05 05:41:56 +02:00
panni cae64feaf9 bump dev 2019-04-05 05:22:06 +02:00
panni 6338757a9b core/providers: re-add addic7ed; add anti-captcha.com solving service 2019-04-05 05:21:06 +02:00
pannal 60eb08c834 Update README.md 2019-04-04 19:31:16 +02:00
panni cbee0bd6c8 bump dev 2019-04-04 19:15:16 +02:00
panni db9da54391 core: http: use implicit cf handling; remove cf handling from providers 2019-04-04 19:14:34 +02:00
panni 5d7f9ced17 providers: disable titlovi (reCAPTCHA) 2019-04-04 17:32:17 +02:00
panni e3825fbf96 bump dev 2019-04-04 16:57:11 +02:00
panni 71db2ae1c9 menu: list subtitles: show subtitles with bad season/episode values as well 2019-04-04 16:56:09 +02:00
panni b7f12e4291 core: cf: add get_live_tokens method for retrieving cookies 2019-04-04 16:46:22 +02:00
panni 20f18fc391 providers: subscene: try reusing cf cookies after successful bypass 2019-04-04 16:45:53 +02:00
panni b6185cc2cc core: http: fix mro 2019-04-04 16:24:34 +02:00
panni 265804a834 core: http: use cfscrape as base session 2019-04-04 16:13:21 +02:00
panni 9e09de5f1e core: cf: randomize user agent on init, not on import 2019-04-04 16:13:04 +02:00
panni db5f85272f core: cf: support 429 and jschl_vc 2019-04-04 16:09:49 +02:00
panni cf2f3d0dca core: cf: add credits 2019-04-04 05:35:32 +02:00
panni 0d46b3fa19 core: add cf magic
providers: subscene: use alternative titles/series for searching; use cf magic
providers: subscene/opensubtitles: use alternative titles/series for searching
2019-04-04 05:33:14 +02:00
panni 768046e889 scan_video: add series/title as alternative by scanning filename itself without parent folders 2019-04-04 04:13:25 +02:00
panni ac940ab25d providers: opensubtitles: show subtitles with possibly mismatched series when manually listing subs 2019-04-04 04:00:46 +02:00
panni 3cee69d23a back to dev 2019-04-04 02:53:43 +02:00
panni deaa164f25 release 2.6.4.2947 2019-04-04 02:53:09 +02:00
panni 68bb7acf95 Merge branch 'develop-2.6'
# Conflicts:
#	Contents/Info.plist
2019-04-04 02:52:08 +02:00
panni 0eefe3200c update changelog 2019-04-04 02:51:03 +02:00
panni df65bae58f core: update certifi to 2019.3.9 (may fix #610) 2019-04-04 02:47:07 +02:00
panni b0443dc812 forgot patch 2019-04-03 21:18:04 +02:00
panni 63545ee732 bump dev 2019-04-03 15:51:06 +02:00
panni 253d099738 providers: opensubtitles: fix only_foreign handling 2019-04-03 15:50:39 +02:00
panni 2ea27f2597 providers: drop support for addic7ed for now 2019-04-03 15:49:46 +02:00
panni 2c4b68d719 core: also clean PYTHONHOME when calling external notification app 2019-04-03 15:48:09 +02:00
panni a70f9c0673 compat: use lowercase paths on subtitle detection 2019-03-04 18:02:06 +01:00
pannal 540a35cb0e Merge pull request #623 from giejay/develop-2.6
Fix issue scandir not returning the name of the file inside Docker
2019-03-04 17:12:44 +01:00
GJ 1d9a2ff6fc Fix issue scandir not returning the name of the file inside Docker images on ARM systems. 2019-03-04 17:01:35 +01:00
panni 01a5d71b4a bump dev 2019-03-02 23:01:00 +01:00
panni 4f11fa53cd core: indentation fix 2019-03-02 22:56:17 +01:00
panni 8f6540118b core: also check for "plex transcoder.exe" in case of windows 2019-03-02 22:37:00 +01:00
panni 089618b8a6 core: use Log.Warn instead of Log.Warning 2019-03-02 02:47:49 +01:00
panni 6f87037c78 bump dev 2019-03-02 01:35:54 +01:00
panni d9b36c0616 core: better plex transcoder path detection 2019-03-02 01:34:02 +01:00
panni df2bc9767c core: search external subtitles: fix condition 2019-02-27 22:03:19 +01:00
panni 508810d5c7 bump dev 2019-02-08 17:38:41 +01:00
panni dc6770ecaa providers: titlovi: fix possibly inexistant reference; break loop on exception 2019-02-08 17:33:02 +01:00
pannal 25b8702a42 Merge pull request #616 from viking1304/develop-2.6
Another fix for Titlovi
2019-02-08 17:29:41 +01:00
viking1304 5a5aa510c5 Log exceptions that might happen while getting search results
Use random user agent string
2019-02-04 23:41:19 +01:00
viking1304 5d7777095e Merge pull request #1 from pannal/develop-2.6
Update Develop 2.6 branch
2019-02-02 23:56:26 +01:00
panni 95ad5b6fbe core: don't raise exception when subtitle not found inside archive 2019-01-27 04:09:43 +01:00
panni 9e3227ba0b bump dev 2019-01-25 14:00:53 +01:00
panni d725c87cae providers: subscene: don't fail on missing cover 2019-01-25 14:00:15 +01:00
panni 6c3bf03bc3 core: extract embedded: fix is_unknown check 2019-01-25 11:59:01 +01:00
panni 20c04f32be core: set _is_valid to False by default 2019-01-15 13:43:33 +01:00
panni 29bafc6215 core: add is_valid shortcut 2019-01-15 13:41:58 +01:00
panni d3279ef923 return None on LanguageError 2019-01-13 05:07:10 +01:00
panni 291e210e63 bump dev 2019-01-13 04:52:05 +01:00
panni 535b1aaba9 core: better embedded streams language detection 2019-01-13 04:51:13 +01:00
panni 48cafadbdd core: auto extract embedded: only use one unknown sub for first language 2019-01-13 04:36:03 +01:00
panni 39d442c2b3 core: SRT parsing: handle ASS color tag in SRT 2019-01-08 13:05:04 +01:00
panni 2bf590b6c4 back from dev 2019-01-05 04:49:07 +01:00
panni 2e80832154 release 2.6.4.2911 2019-01-05 04:48:28 +01:00
panni f64e7c1a61 cleanup #608 2019-01-05 04:39:13 +01:00
pannal 3edf593a18 Merge pull request #608 from jippo015/develop-2.6
continue searching for subs with lang code after und is found
2019-01-05 04:32:24 +01:00
jippo015 7ffe41ae9b Fix: continue searching for embbeded subs after und is found 2018-12-26 16:07:37 +01:00
panni 10a7c327f0 providers: subscene: re-enable search-features for subscene 2018-12-16 05:21:45 +01:00
panni a32a2cabd8 providers: subscene: remove temporarily obsolete season pack search 2018-12-09 17:55:36 +01:00
panni 7d77870daf bump dev 2018-12-09 17:31:20 +01:00
panni 45d7233485 providers: subscene: fix searching; search by release name is currently broken; support year hint for movies 2018-12-09 17:29:48 +01:00
panni d03afb5d47 core: add inflect==2.1.0 2018-12-09 16:56:45 +01:00
panni d5da52d0fb providers: addic7ed: fix not using user credentials; fixes #605 2018-12-09 16:45:18 +01:00
panni 25714acd38 bump dev 2018-12-08 15:00:45 +01:00
panni afe05779cd Merge remote-tracking branch 'origin/develop-2.6' into develop-2.6 2018-12-08 15:00:21 +01:00
panni 8da007f4eb core: make logging for scanning/parsing/preparing videos more clear 2018-12-08 15:00:03 +01:00
pannal 58b2630968 Merge pull request #603 from viking1304/develop-2.6
providers: titlovi: fix provider
2018-12-08 04:40:18 +01:00
panni 05e03b3ea4 submod: common: also match music symbols after a crocodile; move crocodile removal up the chain 2018-12-08 04:01:46 +01:00
panni c2781e834f submod: HI: remove multiple HI_before_colon_caps before one colon 2018-12-08 04:00:59 +01:00
panni 557348831d submod: HI: correctly remove uppercase at start of a sentence when lead by a crocodile (>>); correctly remove lowercase inside brackets when HI matched as well 2018-12-07 22:32:07 +01:00
viking1304 cbf03250f9 Fix typo in code 2018-12-03 05:03:07 +01:00
viking1304 7a3bc7086e Fix subtitle detection in HTML
Skip list elements that are not related to subtitles
2018-12-03 04:48:50 +01:00
viking1304 8047c66869 Update titlovi.py 2018-12-03 03:31:22 +01:00
panni 9d17e2ce9a providers: podnapisi: loosen lxml requirement 2018-11-30 10:16:36 +01:00
panni 1f855a7fd7 providers: podnapisi: fix searching for Marvel series 2018-11-29 12:35:27 +01:00
panni 6310b8f4aa core: don't assume hints["title"] exists 2018-11-28 13:34:19 +01:00
panni 839146b8c7 bump dev 2018-11-27 07:46:52 +01:00
panni 817a6300ea core: improve file cache; use fixed-length cache filenames; fixes #600 2018-11-27 07:46:22 +01:00
panni 3a5effaa52 core: don't log "Checking connections ..." when sonarr/radarr not activated 2018-11-27 07:45:46 +01:00
panni 6e8ce9d23d providers: opensubtitles: improve token logging 2018-11-27 07:45:18 +01:00
panni cae120cfd4 back to dev 2018-11-25 03:30:55 +01:00
panni 734e0f7128 release 2.6.4.2881 2018-11-25 03:30:31 +01:00
panni 4c76439f4e release 2.6.4.2881 2018-11-25 03:23:51 +01:00
panni 2488d4db53 bump dev 2018-11-25 03:23:21 +01:00
panni 565987faff providers: opensubtitles: add advanced setting to optionally not skip subtitles with wrong FPS 2018-11-25 03:21:59 +01:00
panni e14402c6a0 core: extract embedded: fix #598 2018-11-25 03:03:22 +01:00
panni ccfc40f6fc bump dev 2018-11-23 05:18:42 +01:00
panni d69a331b87 Merge remote-tracking branch 'origin/master' into develop-2.6 2018-11-23 05:18:07 +01:00
panni 01fd66c35a core: check sonarr/radarr connectivity without blocking the main thread; fixes #597 2018-11-23 05:15:52 +01:00
pannal 73b33fe697 Merge pull request #582 from morpheus133/Hosszupuskaexception
Refactor the fix_inconsistent_naming function for hosszupuska.
2018-11-20 12:28:52 +01:00
panni 9e730a2b85 back to dev 2018-11-19 17:40:59 +01:00
panni 6395b0e945 Merge remote-tracking branch 'origin/master' into develop-2.6 2018-11-19 17:40:47 +01:00
panni 79d16b98f1 core: scanning: add expected title to series/episodes as well; fix Narcos: Mexico 2018-11-19 17:39:07 +01:00
panni eb4fa8d85d release 2.6.4.2864 2018-11-19 17:14:27 +01:00
panni 7e2d5dfa5d Merge branch 'develop-2.6'
# Conflicts:
#	Contents/Info.plist
2018-11-19 17:14:15 +01:00
panni f785ba8932 update changelog 2018-11-19 17:13:55 +01:00
panni 63cf4a2d67 core: scanning: don't fail on metadata subtitles with bad language code; fixes #596 2018-11-19 17:11:02 +01:00
panni 9e270bb53f providers: legendastv, napiprojekt, subscenter, tvsubtitles: fix "No language to search for" issue; fixes #596 2018-11-19 17:05:53 +01:00
panni 3bafcb6b4e menu: advanced: add skip next search all recently missing subtitles entry 2018-11-19 17:01:35 +01:00
morpheus133 b770a40150 Modification based on comment:
Please modify this PR:
don't remove the sanitize call to not break other providers
add no_sanitize=False to the function to return the unsanitized result
2018-11-19 15:18:22 +01:00
panni 3b50b58aac menu: fix "ignore list list" 2018-11-15 22:28:10 +01:00
morpheus133 20952b5c26 Refactor the fix_inconsistent_naming function for hosszupuska. 2018-09-27 15:14:38 +02:00
245 changed files with 92661 additions and 473 deletions
+86
View File
@@ -1,4 +1,90 @@
2.6.5.3041
Changelog
- core: only reference guessed title if there actually is one
- core: cf: optimize
- core/config: add setting for one existing language to be enough, fixes #491
- core/compat: dns: support nameservers via ENV[dns_resolvers]; don't fall back to default DNS when configured custom DNS failed
- providers: titlovi: prevent repeated captcha solving for CF
2.6.5.3017
Changelog
- core: SRT parsing: handle (bad) ASS color tag in SRT
- core: auto extract embedded: only use one unknown sub for first language
- core: better embedded streams language detection
- core: optimizations
- core: extract embedded: fix is_unknown check
- core: don't raise exception when subtitle not found inside archive
- core: search external subtitles: fix condition
- core: better plex transcoder path detection
- core: use Log.Warn instead of Log.Warning (#619, #629, #633)
- core: also check for "plex transcoder.exe" in case of windows (fixes #619)
- core: auto extract: use mbcs encoding for paths on windows
- core: Fix issue scandir not returning the name of the file inside Docker images on ARM systems. (thanks @giejay)
- core: also clean PYTHONHOME when calling external notification app
- core: update certifi to 2019.3.9
- core: scan_video: add series/title as alternative by scanning filename itself without parent folders
- core: add generic solution for solving captchas using anti captcha services
- core: increase cache time to 180d (was: 30d)
- core: guess_matches: handle multiple title matches; fixes bazarr#403
- windows: fix compatibility issues with plex transcoder
- compat: use lowercase paths on subtitle detection
- providers: addic7ed: re-enable (using paid anti captch service)
- providers: assrt: assume undefined Chinese flavor as Simplified (chs/zho-Hans)
- providers: subscene: make it work again by bypassing cf
- providers: subscene: don't fail on missing cover
- providers: titlovi: re-enable (might need paid anti captch service)
- providers: opensubtitles: fix only_foreign handling
- providers: opensubtitles: show subtitles with possibly mismatched series when manually listing subs
- menu: list subtitles: show subtitles with bad season/episode values as well
- refiners: omdb: fix imdb ids with spaces
2.6.4.2911
- core: improve file cache (windows especially); use fixed-length cache filenames; fixes #600
- core: don't log "Checking connections ..." when sonarr/radarr not activated
- core: make logging for scanning/parsing/preparing videos more clear
- core: extract embedded: continue searching for embbedded subs after undefined language is found (thanks @jippo015)
- compat: core: don't assume hints["title"] exists
- compat: providers: podnapisi: loosen lxml requirement
- providers: addic7ed: fix not using user credentials; fixes #605
- providers: titlovi: fix provider (thanks @viking1304)
- providers: subscene: fix provider
- providers: opensubtitles: improve token logging
- providers: podnapisi: fix searching for Marvel series
- submod: HI: correctly remove uppercase at start of a sentence when lead by a crocodile (>>)
- submod: HI: correctly remove lowercase inside brackets when HI matched as well
- submod: HI: remove multiple HI_before_colon_caps before one colon
- submod: common: also match music symbols after a crocodile; move crocodile removal up the chain
2.6.4.2881
- core: extract embedded: fix automatic extraction not actually writing the subtitles to disk under certain circumstances; fixes #598
- core: sonarr/radarr: don't block the main thread while checking connectivity; fixes #597
- providers: hosszupuska: fix inconsistent series naming (thanks @morpheus133)
- providers: opensubtitles: add advanced setting to optionally not skip subtitles with wrong FPS; fixes #578
2.6.4.2864
- core: scanning: don't fail on metadata subtitles with bad language code; fixes #596
- providers: legendastv, napiprojekt, subscenter, tvsubtitles: fix "No language to search for" issue; fixes #596
- menu: fix "ignore list list"
- menu: advanced: add skip next search all recently missing subtitles entry
2.6.4.2859
- core: fix thread.lock error (only affected the history menu, not the actual functionality)
- core: fix audio-based conditional subtitle decision making; fixes #592
- core: massively improve metadata subtitle storage
- providers: opensubtitles: skip non-forced results when searching for forced
- providers: podnapisi: skip non-forced results when searching for forced
- submod: common: correctly pad music symbols on either side
2.6.4.2834
- core: add option to use custom (Google, Cloudflare) DNS to resolve provider hosts in problematic countries; fixes #547
- core: add support for downloading subtitles only when the audio streams don't match (any?) configured languages; fixes #519
+7 -3
View File
@@ -118,16 +118,20 @@ def agent_extract_embedded(video_part_map):
for plexapi_part in get_all_parts(plexapi_item):
item_count = item_count + 1
used_one_unknown_stream = False
for requested_language in config.lang_list:
embedded_subs = stored_subs.get_by_provider(plexapi_part.id, requested_language, "embedded")
current = stored_subs.get_any(plexapi_part.id, requested_language) or \
requested_language in scanned_video.subtitle_languages
requested_language in scanned_video.external_subtitle_languages
if not embedded_subs:
stream_data = get_embedded_subtitle_streams(plexapi_part, requested_language=requested_language)
stream_data = get_embedded_subtitle_streams(plexapi_part, requested_language=requested_language,
skip_unknown=used_one_unknown_stream)
if stream_data:
stream = stream_data[0]["stream"]
if stream_data[0]["is_unknown"]:
used_one_unknown_stream = True
to_extract.append(({scanned_video: part_info}, plexapi_part, str(stream.index),
str(requested_language), not current))
@@ -224,7 +228,7 @@ class SubZeroAgent(object):
if config.plex_transcoder:
agent_extract_embedded(scanned_video_part_map)
else:
Log.Warning("Plex Transcoder not found, can't auto extract")
Log.Warn("Plex Transcoder not found, can't auto extract")
# clear missing subtitles menu data
if not scheduler.is_task_running("MissingSubtitles"):
+17
View File
@@ -61,6 +61,10 @@ def AdvancedMenu(randomize=None, header=None, message=None):
key=Callback(SkipFindBetterSubtitles, randomize=timestamp()),
title=pad_title(_("Skip next find better subtitles (sets last run to now)")),
))
oc.add(DirectoryObject(
key=Callback(SkipRecentlyAddedMissing, randomize=timestamp()),
title=pad_title(_("Skip next find recently added with missing subtitles (sets last run to now)")),
))
oc.add(DirectoryObject(
key=Callback(TriggerStorageMaintenance, randomize=timestamp()),
title=pad_title(_("Trigger subtitle storage maintenance")),
@@ -207,6 +211,19 @@ def SkipFindBetterSubtitles(randomize=None):
)
@route(PREFIX + '/skipram')
@debounce
def SkipRecentlyAddedMissing(randomize=None):
task = scheduler.task("SearchAllRecentlyAddedMissing")
task.last_run = datetime.datetime.now()
return AdvancedMenu(
randomize=timestamp(),
header=_("Success"),
message=_("SearchAllRecentlyAddedMissing skipped")
)
@route(PREFIX + '/triggermaintenance')
@debounce
def TriggerStorageMaintenance(randomize=None):
+13 -2
View File
@@ -580,6 +580,8 @@ def ListAvailableSubsForItemMenu(rating_key=None, part_id=None, title=None, item
bl_addon = "Blacklisted "
wrong_fps_addon = ""
wrong_series_addon = ""
wrong_season_ep_addon = ""
if subtitle.wrong_fps:
if plex_part:
wrong_fps_addon = _(" (wrong FPS, sub: %(subtitle_fps)s, media: %(media_fps)s)",
@@ -589,15 +591,24 @@ def ListAvailableSubsForItemMenu(rating_key=None, part_id=None, title=None, item
wrong_fps_addon = _(" (wrong FPS, sub: %(subtitle_fps)s, media: unknown, low impact mode)",
subtitle_fps=subtitle.fps)
if subtitle.wrong_series:
wrong_series_addon = _(" (possibly wrong series)")
if subtitle.wrong_season_ep:
wrong_season_ep_addon = _(" (possibly wrong season/episode)")
oc.add(DirectoryObject(
key=Callback(TriggerDownloadSubtitle, rating_key=rating_key, randomize=timestamp(), item_title=item_title,
subtitle_id=str(subtitle.id), language=language),
title=_(u"%(blacklisted_state)s%(current_state)s: %(provider_name)s, score: %(score)s%(wrong_fps_state)s",
title=_(u"%(blacklisted_state)s%(current_state)s: %(provider_name)s, score: %(score)s%(wrong_fps_state)s"
u"%(wrong_series_state)s%(wrong_season_ep_state)s",
blacklisted_state=bl_addon,
current_state=_("Available") if current_id != subtitle.id else _("Current"),
provider_name=_(subtitle.provider_name),
score=subtitle.score,
wrong_fps_state=wrong_fps_addon),
wrong_fps_state=wrong_fps_addon,
wrong_series_state=wrong_series_addon,
wrong_season_ep_state=wrong_season_ep_addon),
summary=_(u"Release: %(release_info)s, Matches: %(matches)s",
release_info=subtitle.release_info,
matches=", ".join(subtitle.matches)),
+40 -24
View File
@@ -276,6 +276,40 @@ def replace_item(obj, key, replace_value):
return obj
def check_connections():
# debug drone
Log.Debug("Checking connections ...")
log_buffer = []
try:
from subliminal_patch.refiners.drone import SonarrClient, RadarrClient
log_buffer.append(["----- Connections -----"])
for key, cls in [("sonarr", SonarrClient), ("radarr", RadarrClient)]:
if key in config.refiner_settings:
cname = key.capitalize()
try:
status = cls(**config.refiner_settings[key]).status(timeout=5)
except HTTPError, e:
if e.response.status_code == 401:
log_buffer.append(("%s: NOT WORKING - BAD API KEY", cname))
else:
log_buffer.append(("%s: NOT WORKING - %s", cname, traceback.format_exc()))
except:
log_buffer.append(("%s: NOT WORKING - %s", cname, traceback.format_exc()))
else:
if status and status["version"]:
log_buffer.append(("%s: OK - %s", cname, status["version"]))
else:
log_buffer.append(("%s: NOT WORKING - %s", cname))
except:
log_buffer.append(("Something went really wrong when evaluating Sonarr/Radarr: %s", traceback.format_exc()))
finally:
Core.log.setLevel(logging.DEBUG)
for entry in log_buffer:
Log.Debug(*entry)
Core.log.setLevel(logging.getLevelName(Prefs["log_level"]))
@route(PREFIX + '/ValidatePrefs', enforce_route=True)
def ValidatePrefs():
Core.log.setLevel(logging.DEBUG)
@@ -333,7 +367,8 @@ def ValidatePrefs():
"enable_channel", "permissions_ok", "missing_permissions", "fs_encoding",
"subtitle_destination_folder", "include", "include_exclude_paths", "include_exclude_sz_files",
"new_style_cache", "dbm_supported", "lang_list", "providers", "normal_subs", "forced_only", "forced_also",
"plex_transcoder", "refiner_settings", "unrar", "adv_cfg_path", "use_custom_dns"]:
"plex_transcoder", "refiner_settings", "unrar", "adv_cfg_path", "use_custom_dns",
"has_anticaptcha", "anticaptcha_cls"]:
value = getattr(config, attr)
if isinstance(value, dict):
@@ -341,6 +376,9 @@ def ValidatePrefs():
Log.Debug("config.%s: %s", attr, d)
continue
if attr in ("api_key",):
value = "xxxxxxxxxxxxxxxxxxxxxxxxx"
Log.Debug("config.%s: %s", attr, value)
for attr in ["plugin_log_path", "server_log_path"]:
@@ -362,30 +400,8 @@ def ValidatePrefs():
"subtitles.save.filesystem", ]:
Log.Debug("Pref.%s: %s", attr, Prefs[attr])
# debug drone
if "sonarr" in config.refiner_settings or "radarr" in config.refiner_settings:
Log.Debug("----- Connections -----")
try:
from subliminal_patch.refiners.drone import SonarrClient, RadarrClient
for key, cls in [("sonarr", SonarrClient), ("radarr", RadarrClient)]:
if key in config.refiner_settings:
cname = key.capitalize()
try:
status = cls(**config.refiner_settings[key]).status()
except HTTPError, e:
if e.response.status_code == 401:
Log.Debug("%s: NOT WORKING - BAD API KEY", cname)
else:
Log.Debug("%s: NOT WORKING - %s", cname, traceback.format_exc())
except:
Log.Debug("%s: NOT WORKING - %s", cname, traceback.format_exc())
else:
if status and status["version"]:
Log.Debug("%s: OK - %s", cname, status["version"])
else:
Log.Debug("%s: NOT WORKING - %s", cname)
except:
Log.Debug("Something went really wrong when evaluating Sonarr/Radarr: %s", traceback.format_exc())
Thread.Create(check_connections)
# fixme: check existance of and os access of logs
Log.Debug("----- Environment -----")
+10 -2
View File
@@ -7,11 +7,12 @@ import os
import operator
from func import enable_channel_wrapper, route_wrapper, register_route_function
from subzero.lib.io import get_viable_encoding
from subzero.language import Language
from support.i18n import is_localized_string, _
from support.items import get_kind, get_item_thumb, get_item, get_item_kind_from_item, refresh_item
from support.helpers import get_video_display_title, pad_title, display_language, quote_args, is_stream_forced, \
get_title_for_video_metadata
get_title_for_video_metadata, mswindows
from support.history import get_history
from support.ignore import get_decision_list
from support.lib import get_intent
@@ -193,9 +194,16 @@ def extract_embedded_sub(**kwargs):
args = [
config.plex_transcoder, "-i", part.file, "-map", "0:%s" % stream_index, "-f", out_codec, "-"
]
cmdline = quote_args(args)
Log.Debug(u"Calling: %s", cmdline)
if mswindows:
Log.Debug("MSWindows: Fixing encoding")
cmdline = cmdline.encode("mbcs")
output = None
try:
output = subprocess.check_output(quote_args(args), stderr=subprocess.PIPE, shell=True)
output = subprocess.check_output(cmdline, stderr=subprocess.PIPE, shell=True)
except:
Log.Error("Extraction failed: %s", traceback.format_exc())
+73 -29
View File
@@ -1,5 +1,6 @@
# coding=utf-8
import copy
import json
import os
import re
import inspect
@@ -79,7 +80,7 @@ PROVIDER_THROTTLE_MAP = {
class Config(object):
config_version = 2
config_version = 3
libraries_root = None
plugin_info = ""
version = None
@@ -143,11 +144,15 @@ class Config(object):
refiner_settings = None
exact_filenames = False
only_one = False
any_language_is_enough = False
embedded_auto_extract = False
ietf_as_alpha3 = False
unrar = None
adv_cfg_path = None
use_custom_dns = False
use_custom_dns = None
anticaptcha_token = None
anticaptcha_cls = None
has_anticaptcha = False
store_recently_played_amount = 40
@@ -172,6 +177,8 @@ class Config(object):
self.data_items_path = os.path.join(self.data_path, "DataItems")
self.universal_plex_token = self.get_universal_plex_token()
self.plex_token = os.environ.get("PLEXTOKEN", self.universal_plex_token)
self.new_style_cache = cast_bool(Prefs['new_style_cache'])
self.pack_cache_dir = self.get_pack_cache_dir()
try:
self.migrate_prefs()
except:
@@ -180,12 +187,15 @@ class Config(object):
subzero.constants.DEFAULT_TIMEOUT = lib.DEFAULT_TIMEOUT = self.pms_request_timeout = \
min(cast_int(Prefs['pms_request_timeout'], 15), 45)
self.low_impact_mode = cast_bool(Prefs['low_impact_mode'])
self.new_style_cache = cast_bool(Prefs['new_style_cache'])
self.pack_cache_dir = self.get_pack_cache_dir()
self.advanced = self.get_advanced_config()
self.debug_i18n = self.advanced.debug_i18n
os.environ["SZ_USER_AGENT"] = self.get_user_agent()
os.environ["ANTICAPTCHA_ACCOUNT_KEY"] = self.anticaptcha_token = str(Prefs["anticaptcha.api_key"]) or ""
acs = str(Prefs["anticaptcha.service"])
if acs and acs != "none":
os.environ["ANTICAPTCHA_CLASS"] = self.anticaptcha_cls = acs
self.has_anticaptcha = self.anticaptcha_token and self.anticaptcha_cls
self.setup_proxies()
self.set_plugin_mode()
@@ -225,9 +235,10 @@ class Config(object):
self.no_refresh = os.environ.get("SZ_NO_REFRESH", False)
self.plex_transcoder = self.get_plex_transcoder()
self.only_one = cast_bool(Prefs['subtitles.only_one'])
self.any_language_is_enough = Prefs['subtitles.any_language_is_enough']
self.embedded_auto_extract = cast_bool(Prefs["subtitles.embedded.autoextract"])
self.ietf_as_alpha3 = cast_bool(Prefs["subtitles.language.ietf_normalize"])
self.use_custom_dns = cast_bool(Prefs['use_custom_dns'])
self.use_custom_dns = self.parse_custom_dns()
self.initialized = True
def migrate_prefs(self):
@@ -256,6 +267,9 @@ class Config(object):
if update_prefs:
update_user_prefs(update_prefs, Prefs, Log)
else:
Dict["config_version"] = self.config_version
Dict.Save()
def migrate_prefs_to_1(self, user_prefs, **kwargs):
update_prefs = {}
@@ -275,6 +289,15 @@ class Config(object):
return update_prefs
def migrate_prefs_to_3(self, user_prefs, **kwargs):
if config.new_style_cache:
self.init_cache()
try:
subliminal.region.backend.clear()
except:
pass
return {}
def init_libraries(self):
try_executables = []
custom_unrar = os.environ.get("SZ_UNRAR_TOOL")
@@ -317,9 +340,10 @@ class Config(object):
def init_cache(self):
if self.new_style_cache:
subliminal.region.configure('subzero.cache.file', expiration_time=datetime.timedelta(days=30),
subliminal.region.configure('subzero.cache.file', expiration_time=datetime.timedelta(days=180),
arguments={'appname': "sz_cache",
'app_cache_dir': self.data_path})
'app_cache_dir': self.data_path},
replace_existing_backend=True)
Log.Info("Using new style file based cache!")
return
@@ -359,14 +383,15 @@ class Config(object):
try:
subliminal.region.configure('dogpile.cache.dbm', expiration_time=datetime.timedelta(days=30),
arguments={'filename': dbfn,
'lock_factory': MutexLock})
'lock_factory': MutexLock},
replace_existing_backend=True)
Log.Info("Using file based cache!")
return
except:
self.dbm_supported = False
Log.Warn("Not using file based cache!")
subliminal.region.configure('dogpile.cache.memory')
subliminal.region.configure('dogpile.cache.memory', replace_existing_backend=True)
def sync_cache(self):
if not self.new_style_cache:
@@ -737,7 +762,7 @@ class Config(object):
# 'thesubdb': Prefs['provider.thesubdb.enabled'],
'podnapisi': cast_bool(Prefs['provider.podnapisi.enabled']),
'titlovi': cast_bool(Prefs['provider.titlovi.enabled']),
'addic7ed': cast_bool(Prefs['provider.addic7ed.enabled']),
'addic7ed': cast_bool(Prefs['provider.addic7ed.enabled']) and self.has_anticaptcha,
'tvsubtitles': cast_bool(Prefs['provider.tvsubtitles.enabled']),
'legendastv': cast_bool(Prefs['provider.legendastv.enabled']),
'napiprojekt': cast_bool(Prefs['provider.napiprojekt.enabled']),
@@ -817,11 +842,13 @@ class Config(object):
def get_provider_settings(self):
os_use_https = self.advanced.providers.opensubtitles.use_https \
if self.advanced.providers.opensubtitles.use_https != None else True
if self.advanced.providers.opensubtitles.use_https is not None else True
os_skip_wrong_fps = self.advanced.providers.opensubtitles.skip_wrong_fps \
if self.advanced.providers.opensubtitles.skip_wrong_fps is not None else True
provider_settings = {'addic7ed': {'username': Prefs['provider.addic7ed.username'],
'password': Prefs['provider.addic7ed.password'],
'use_random_agents': cast_bool(Prefs['provider.addic7ed.use_random_agents1']),
},
'opensubtitles': {'username': Prefs['provider.opensubtitles.username'],
'password': Prefs['provider.opensubtitles.password'],
@@ -831,6 +858,7 @@ class Config(object):
'is_vip': cast_bool(Prefs['provider.opensubtitles.is_vip']),
'use_ssl': os_use_https,
'timeout': self.advanced.providers.opensubtitles.timeout or 15,
'skip_wrong_fps': os_skip_wrong_fps,
},
'podnapisi': {
'only_foreign': self.forced_only,
@@ -970,27 +998,37 @@ class Config(object):
self.activity_mode = "next_episode"
def get_plex_transcoder(self):
paths = []
base_path = os.environ.get("PLEX_MEDIA_SERVER_HOME", None)
if not base_path:
# fall back to bundled plugins path
bundle_path = os.environ.get("PLEXBUNDLEDPLUGINSPATH", None)
if bundle_path:
base_path = os.path.normpath(os.path.join(bundle_path, "..", ".."))
if base_path:
paths.append(base_path)
bundle_path = os.environ.get("PLEXBUNDLEDPLUGINSPATH", None)
if bundle_path:
paths.append(os.path.normpath(os.path.join(bundle_path, "..", "..")))
paths.append(self.app_support_path)
bns = []
if sys.platform == "darwin":
fn = os.path.join(base_path, "MacOS", "Plex Transcoder")
bns.append(("MacOS", "Plex Transcoder"))
elif mswindows:
fn = os.path.join(base_path, "plextranscoder.exe")
bns = [("plextranscoder.exe",), ("plex transcoder.exe",)]
else:
fn = os.path.join(base_path, "Plex Transcoder")
bns.append(("Plex Transcoder",))
if os.path.isfile(fn):
return fn
for path in paths:
for bn in bns:
fn = os.path.join(path, *bn)
# look inside Resources folder as fallback, as well
fn = os.path.join(base_path, "Resources", "Plex Transcoder")
if os.path.isfile(fn):
return fn
if os.path.isfile(fn):
return fn
# look inside Resources folder as fallback, as well
for vbn in ("Plex Transcoder", "plextranscoder.exe", "plex transcoder.exe"):
fn = os.path.join(path, "Resources", vbn)
if os.path.isfile(fn):
return fn
def parse_rename_mode(self):
# fixme: exact_filenames should be determined via callback combined with info about the current video
@@ -1043,6 +1081,15 @@ class Config(object):
def text_based_formats(self):
return self.advanced.text_subtitle_formats or TEXT_SUBTITLE_EXTS
def parse_custom_dns(self):
custom_dns = Prefs['use_custom_dns2'].strip()
os.environ["dns_resolvers"] = ""
if custom_dns:
ips = filter(lambda x: x, [d.strip() for d in custom_dns.split(",")])
if ips:
os.environ["dns_resolvers"] = json.dumps(ips)
return os.environ["dns_resolvers"]
def init_subliminal_patches(self):
# configure custom subtitle destination folders for scanning pre-existing subs
Log.Debug("Patching subliminal ...")
@@ -1053,9 +1100,6 @@ class Config(object):
subliminal_patch.core.DOWNLOAD_TRIES = int(Prefs['subtitles.try_downloads'])
subliminal.score.episode_scores["addic7ed_boost"] = int(Prefs['provider.addic7ed.boost_by2'])
if self.use_custom_dns:
subliminal_patch.http.set_custom_resolver()
config = Config()
config.initialize()
+16
View File
@@ -46,6 +46,22 @@ def get_missing_languages(video, part):
missing_languages = (languages - have_languages)
if config.any_language_is_enough != "Always search for all configured languages":
not_in_forced = "foreign" in config.any_language_is_enough
if "External or embedded subtitle" in config.any_language_is_enough:
langs = video.subtitle_languages if not not_in_forced else \
filter(lambda l: not l.forced, video.subtitle_languages)
if langs:
Log.Debug("We have at least one subtitle for any configured language.")
return False
elif "External subtitle" in config.any_language_is_enough:
langs = video.subtitle_languages if not not_in_forced else \
filter(lambda l: not l.forced, video.external_subtitle_languages)
if langs:
Log.Debug("We have at least one external subtitle for any configured language.")
return False
# all languages are found if we either really have subs for all languages or we only want to have exactly one language
# and we've only found one (the case for a selected language, Prefs['subtitles.only_one'] (one found sub matches any language))
found_one_which_is_enough = len(video.subtitle_languages) >= 1 and Prefs['subtitles.only_one']
+14 -3
View File
@@ -12,10 +12,12 @@ import subprocess
import sys
from collections import OrderedDict
from babelfish.exceptions import LanguageError
import chardet
from bs4 import UnicodeDammit
from subzero.language import Language
from subzero.language import Language, language_from_stream
from subzero.analytics import track_event
mswindows = (sys.platform == "win32")
@@ -274,6 +276,8 @@ def get_item_hints(data):
"title": data["original_title"] or data["series"],
}
)
if hints["title"]:
hints["title"] = hints["title"].replace(":", "")
return hints
@@ -282,6 +286,7 @@ def notify_executable(exe_info, videos, subtitles, storage):
"subtitle_language", "subtitle_path", "subtitle_filename", "provider", "score", "storage", "series_id",
"series", "title", "section", "filename", "path", "folder", "season_id", "type", "id", "season"
)
to_clean = ("PYTHONPATH", "PYTHONHOME")
exe, arguments = exe_info
for video, video_subtitles in subtitles.items():
for subtitle in video_subtitles:
@@ -317,8 +322,9 @@ def notify_executable(exe_info, videos, subtitles, storage):
env = dict(os.environ)
# clean out any Plex-PYTHONPATH that may bleed through the spawned process
if "PYTHONPATH" in env and "plex" in env["PYTHONPATH"].lower():
del env["PYTHONPATH"]
for v in to_clean:
if v in env and "plex" in env[v].lower():
del env[v]
try:
proc = subprocess.Popen(quote_args([exe] + prepared_arguments), stdout=subprocess.PIPE,
@@ -386,6 +392,11 @@ def get_language_from_stream(lang_code):
if lang and lang != "xx":
# Log.Debug("Found language: %r", lang)
return Language.fromietf(lang)
elif lang:
try:
return language_from_stream(lang)
except LanguageError:
pass
def audio_streams_match_languages(video, languages):
+9 -2
View File
@@ -174,9 +174,11 @@ def get_all_parts(plex_item):
return parts
def get_embedded_subtitle_streams(part, requested_language=None, skip_duplicate_unknown=True):
def get_embedded_subtitle_streams(part, requested_language=None, skip_duplicate_unknown=True, skip_unknown=False):
streams = []
streams_unknown = []
has_unknown = False
found_requested_language = False
for stream in part.streams:
# subtitle stream
if stream.stream_type == 3 and not stream.stream_key and stream.codec in TEXT_SUBTITLE_EXTS:
@@ -196,14 +198,19 @@ def get_embedded_subtitle_streams(part, requested_language=None, skip_duplicate_
language = Language.rebuild(list(config.lang_list)[0], forced=is_forced)
is_unknown = True
has_unknown = True
streams_unknown.append({"stream": stream, "is_unknown": is_unknown, "language": language,
"is_forced": is_forced})
if not requested_language or found_requested_language or has_unknown:
if not requested_language or found_requested_language:
streams.append({"stream": stream, "is_unknown": is_unknown, "language": language,
"is_forced": is_forced})
if found_requested_language:
break
if streams_unknown and not found_requested_language and not skip_unknown:
streams = streams_unknown
return streams
+13 -6
View File
@@ -12,7 +12,7 @@ from subzero.video import parse_video, set_existing_languages
from subzero.language import language_from_stream, Language
def scan_video(pms_video_info, ignore_all=False, hints=None, rating_key=None, providers=None, skip_hashing=False):
def prepare_video(pms_video_info, ignore_all=False, hints=None, rating_key=None, providers=None, skip_hashing=False):
"""
returnes a subliminal/guessit-refined parsed video
:param pms_video_info:
@@ -29,7 +29,7 @@ def scan_video(pms_video_info, ignore_all=False, hints=None, rating_key=None, pr
if ignore_all:
Log.Debug("Force refresh intended.")
Log.Debug("Scanning video: %s, external_subtitles=%s, embedded_subtitles=%s" % (
Log.Debug("Detecting streams: %s, external_subtitles=%s, embedded_subtitles=%s" % (
plex_part.file, external_subtitles, embedded_subtitles))
known_embedded = []
@@ -90,7 +90,14 @@ def scan_video(pms_video_info, ignore_all=False, hints=None, rating_key=None, pr
known_metadata_subs = set()
meta_subs = get_subtitles_from_metadata(plex_part)
for language, subList in meta_subs.iteritems():
lang = Language.fromietf(Locale.Language.Match(language))
try:
lang = Language.fromietf(Locale.Language.Match(language))
except LanguageError:
if config.treat_und_as_first:
lang = Language.rebuild(list(config.lang_list)[0])
else:
continue
if subList:
for key in subList:
if key.startswith("subzero_md_forced"):
@@ -147,9 +154,9 @@ def scan_videos(videos, ignore_all=False, providers=None, skip_hashing=False):
hints = helpers.get_item_hints(video)
video["plex_part"].fps = get_stream_fps(video["plex_part"].streams)
p = providers or config.get_providers(media_type="series" if video["type"] == "episode" else "movies")
scanned_video = scan_video(video, ignore_all=force_refresh or ignore_all, hints=hints,
rating_key=video["id"], providers=p,
skip_hashing=skip_hashing)
scanned_video = prepare_video(video, ignore_all=force_refresh or ignore_all, hints=hints,
rating_key=video["id"], providers=p,
skip_hashing=skip_hashing)
if not scanned_video:
continue
+1 -1
View File
@@ -33,7 +33,7 @@ def store_subtitle_info(scanned_video_part_map, downloaded_subtitles, storage_ty
video_id = str(video.id)
plex_item = get_item(video_id)
if not plex_item:
Log.Warning("Plex item not found: %s", video_id)
Log.Warn("Plex item not found: %s", video_id)
continue
metadata = video.plexapi_metadata
+5 -3
View File
@@ -165,8 +165,10 @@ class SubtitleListingMixin(object):
can_verify_series = False
if can_verify_series and not {"series", "season", "episode"}.issubset(matches):
Log.Debug(u"%s: Skipping %s, because it doesn't match our series/episode", self.name, s)
continue
if "series" not in matches:
s.wrong_series = True
else:
s.wrong_season_ep = True
unsorted_subtitles.append(
(s, compute_score(matches, s, video, hearing_impaired=use_hearing_impaired), matches))
@@ -175,7 +177,7 @@ class SubtitleListingMixin(object):
subtitles = []
for subtitle, score, matches in scored_subtitles:
# check score
if score < min_score:
if score < min_score and not subtitle.wrong_series:
Log.Info(u'%s: Score %d is below min_score (%d)', self.name, score, min_score)
continue
subtitle.score = score
+37 -13
View File
@@ -205,6 +205,19 @@
],
"default": "Never"
},
{
"id": "subtitles.any_language_is_enough",
"label": "Don't search for subtitles if a subtitle in any configured language exists as",
"type": "enum",
"values": [
"External or embedded subtitle",
"External or embedded subtitle (not foreign/forced)",
"External subtitle",
"External subtitle (not foreign/forced)",
"Always search for all configured languages"
],
"default": "Always search for all configured languages"
},
{
"id": "subtitles.language.ietf_display",
"label": "Display languages with country attribute as ISO 639-1 (e.g. pt-BR = pt)",
@@ -273,6 +286,23 @@
"type": "text",
"default": ""
},
{
"id": "anticaptcha.service",
"label": "AntiCaptcha-Service (needs paid account; enables Addic7ed, titlovi)",
"type": "enum",
"values": [
"none",
"anti-captcha.com",
"deathbycaptcha.com"
],
"default": "none"
},
{
"id": "anticaptcha.api_key",
"label": "AntiCaptcha-Service key (anti-captcha.com: account_key; deathbycaptcha.com: username:password)",
"type": "text",
"default": ""
},
{
"id": "provider.opensubtitles.enabled",
"label": "Provider: Enable OpenSubtitles",
@@ -305,15 +335,9 @@
"type": "bool",
"default": "true"
},
{
"id": "provider.titlovi.enabled",
"label": "Provider: Enable Titlovi.com",
"type": "bool",
"default": "true"
},
{
"id": "provider.addic7ed.enabled",
"label": "Provider: Enable Addic7ed",
"label": "Provider: Enable Addic7ed (needs AntiCaptcha)",
"type": "bool",
"default": "true"
},
@@ -364,8 +388,8 @@
"default": "19"
},
{
"id": "provider.addic7ed.use_random_agents1",
"label": "Addic7ed: Use random user agents",
"id": "provider.titlovi.enabled",
"label": "Provider: Enable Titlovi.com (might need AntiCaptcha)",
"type": "bool",
"default": "true"
},
@@ -836,10 +860,10 @@
"default": "15"
},
{
"id": "use_custom_dns",
"label": "Use Google DNS (for \"problematic\" countries)",
"type": "bool",
"default": "false"
"id": "use_custom_dns2",
"label": "Use custom DNS (IPs, comma-separated, leave empty for system DNS. Default: Google/CF)",
"type": "text",
"default": "1.1.1.1, 8.8.8.8"
},
{
"id": "proxy",
+5 -5
View File
@@ -9,11 +9,11 @@
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleShortVersionString</key>
<string>2.6.4</string>
<string>2.6.5</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>2.6.4.2859</string>
<string>2.6.5.3074</string>
<key>PlexFrameworkVersion</key>
<string>2</string>
<key>PlexPluginClass</key>
@@ -23,7 +23,7 @@
<key>PlexPluginConsoleLogging</key>
<string>0</string>
<key>PlexPluginDevMode</key>
<string>0</string>
<string>1</string>
<key>PlexPluginCodePolicy</key>
<!-- this allows channels to access some python methods which are otherwise blocked, as well as import external code libraries, and interact with the PMS HTTP API -->
<string>Elevated</string>
@@ -32,7 +32,7 @@
&lt;h1&gt;Sub-Zero for Plex&lt;/h1&gt;&lt;i&gt;Subtitles done right&lt;/i&gt;
Version 2.6.4.2859
Version 2.6.5.3074 DEV
Originally based on @bramwalet's awesome &lt;a href=&quot;https://github.com/bramwalet/Subliminal.bundle&quot;&gt;Subliminal.bundle&lt;/a&gt;
@@ -46,7 +46,7 @@ Github: &lt;a href=&quot;https://github.com/pannal/Sub-Zero.bundle&quot;&gt;http
3rd party licenses: &lt;a href=&quot;https://github.com/pannal/Sub-Zero.bundle/tree/master/Licenses&quot;&gt;https://github.com/pannal/Sub-Zero.bundle/tree/master/Licenses&lt;/a&gt;
panni, 2018
panni, 2019
&lt;/div&gt;
</string>
</dict>
@@ -1,3 +1,3 @@
from .core import where, old_where
from .core import where
__version__ = "2018.10.15"
__version__ = "2019.03.09"
@@ -4268,3 +4268,391 @@ rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV
57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg
Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9
-----END CERTIFICATE-----
# Issuer: CN=GTS Root R1 O=Google Trust Services LLC
# Subject: CN=GTS Root R1 O=Google Trust Services LLC
# Label: "GTS Root R1"
# Serial: 146587175971765017618439757810265552097
# MD5 Fingerprint: 82:1a:ef:d4:d2:4a:f2:9f:e2:3d:97:06:14:70:72:85
# SHA1 Fingerprint: e1:c9:50:e6:ef:22:f8:4c:56:45:72:8b:92:20:60:d7:d5:a7:a3:e8
# SHA256 Fingerprint: 2a:57:54:71:e3:13:40:bc:21:58:1c:bd:2c:f1:3e:15:84:63:20:3e:ce:94:bc:f9:d3:cc:19:6b:f0:9a:54:72
-----BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgIQbkepxUtHDA3sM9CJuRz04TANBgkqhkiG9w0BAQwFADBH
MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM
QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy
MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl
cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM
f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vX
mX7wCl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7
zUjwTcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0P
fyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtc
vfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4
Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUsp
zBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOO
Rc92wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYW
k70paDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+
DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgF
lQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
HQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBADiW
Cu49tJYeX++dnAsznyvgyv3SjgofQXSlfKqE1OXyHuY3UjKcC9FhHb8owbZEKTV1
d5iyfNm9dKyKaOOpMQkpAWBz40d8U6iQSifvS9efk+eCNs6aaAyC58/UEBZvXw6Z
XPYfcX3v73svfuo21pdwCxXu11xWajOl40k4DLh9+42FpLFZXvRq4d2h9mREruZR
gyFmxhE+885H7pwoHyXa/6xmld01D1zvICxi/ZG6qcz8WpyTgYMpl0p8WnK0OdC3
d8t5/Wk6kjftbjhlRn7pYL15iJdfOBL07q9bgsiG1eGZbYwE8na6SfZu6W0eX6Dv
J4J2QPim01hcDyxC2kLGe4g0x8HYRZvBPsVhHdljUEn2NIVq4BjFbkerQUIpm/Zg
DdIx02OYI5NaAIFItO/Nis3Jz5nu2Z6qNuFoS3FJFDYoOj0dzpqPJeaAcWErtXvM
+SUWgeExX6GjfhaknBZqlxi9dnKlC54dNuYvoS++cJEPqOba+MSSQGwlfnuzCdyy
F62ARPBopY+Udf90WuioAnwMCeKpSwughQtiue+hMZL77/ZRBIls6Kl0obsXs7X9
SQ98POyDGCBDTtWTurQ0sR8WNh8M5mQ5Fkzc4P4dyKliPUDqysU0ArSuiYgzNdws
E3PYJ/HQcu51OyLemGhmW/HGY0dVHLqlCFF1pkgl
-----END CERTIFICATE-----
# Issuer: CN=GTS Root R2 O=Google Trust Services LLC
# Subject: CN=GTS Root R2 O=Google Trust Services LLC
# Label: "GTS Root R2"
# Serial: 146587176055767053814479386953112547951
# MD5 Fingerprint: 44:ed:9a:0e:a4:09:3b:00:f2:ae:4c:a3:c6:61:b0:8b
# SHA1 Fingerprint: d2:73:96:2a:2a:5e:39:9f:73:3f:e1:c7:1e:64:3f:03:38:34:fc:4d
# SHA256 Fingerprint: c4:5d:7b:b0:8e:6d:67:e6:2e:42:35:11:0b:56:4e:5f:78:fd:92:ef:05:8c:84:0a:ea:4e:64:55:d7:58:5c:60
-----BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgIQbkepxlqz5yDFMJo/aFLybzANBgkqhkiG9w0BAQwFADBH
MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM
QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy
MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl
cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3Lv
CvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3Kg
GjSY6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9Bu
XvAuMC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOd
re7kRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXu
PuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1
mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K
8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqj
x5RWIr9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsR
nTKaG73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0
kzCqgc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9Ok
twIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
HQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBALZp
8KZ3/p7uC4Gt4cCpx/k1HUCCq+YEtN/L9x0Pg/B+E02NjO7jMyLDOfxA325BS0JT
vhaI8dI4XsRomRyYUpOM52jtG2pzegVATX9lO9ZY8c6DR2Dj/5epnGB3GFW1fgiT
z9D2PGcDFWEJ+YF59exTpJ/JjwGLc8R3dtyDovUMSRqodt6Sm2T4syzFJ9MHwAiA
pJiS4wGWAqoC7o87xdFtCjMwc3i5T1QWvwsHoaRc5svJXISPD+AVdyx+Jn7axEvb
pxZ3B7DNdehyQtaVhJ2Gg/LkkM0JR9SLA3DaWsYDQvTtN6LwG1BUSw7YhN4ZKJmB
R64JGz9I0cNv4rBgF/XuIwKl2gBbbZCr7qLpGzvpx0QnRY5rn/WkhLx3+WuXrD5R
RaIRpsyF7gpo8j5QOHokYh4XIDdtak23CZvJ/KRY9bb7nE4Yu5UC56GtmwfuNmsk
0jmGwZODUNKBRqhfYlcsu2xkiAhu7xNUX90txGdj08+JN7+dIPT7eoOboB6BAFDC
5AwiWVIQ7UNWhwD4FFKnHYuTjKJNRn8nxnGbJN7k2oaLDX5rIMHAnuFl2GqjpuiF
izoHCBy69Y9Vmhh1fuXsgWbRIXOhNUQLgD1bnF5vKheW0YMjiGZt5obicDIvUiLn
yOd/xCxgXS/Dr55FBcOEArf9LAhST4Ldo/DUhgkC
-----END CERTIFICATE-----
# Issuer: CN=GTS Root R3 O=Google Trust Services LLC
# Subject: CN=GTS Root R3 O=Google Trust Services LLC
# Label: "GTS Root R3"
# Serial: 146587176140553309517047991083707763997
# MD5 Fingerprint: 1a:79:5b:6b:04:52:9c:5d:c7:74:33:1b:25:9a:f9:25
# SHA1 Fingerprint: 30:d4:24:6f:07:ff:db:91:89:8a:0b:e9:49:66:11:eb:8c:5e:46:e5
# SHA256 Fingerprint: 15:d5:b8:77:46:19:ea:7d:54:ce:1c:a6:d0:b0:c4:03:e0:37:a9:17:f1:31:e8:a0:4e:1e:6b:7a:71:ba:bc:e5
-----BEGIN CERTIFICATE-----
MIICDDCCAZGgAwIBAgIQbkepx2ypcyRAiQ8DVd2NHTAKBggqhkjOPQQDAzBHMQsw
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
MBIGA1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQA
IgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout
736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2A
DDL24CejQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
DgQWBBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEAgFuk
fCPAlaUs3L6JbyO5o91lAFJekazInXJ0glMLfalAvWhgxeG4VDvBNhcl2MG9AjEA
njWSdIUlUfUk7GRSJFClH9voy8l27OyCbvWFGFPouOOaKaqW04MjyaR7YbPMAuhd
-----END CERTIFICATE-----
# Issuer: CN=GTS Root R4 O=Google Trust Services LLC
# Subject: CN=GTS Root R4 O=Google Trust Services LLC
# Label: "GTS Root R4"
# Serial: 146587176229350439916519468929765261721
# MD5 Fingerprint: 5d:b6:6a:c4:60:17:24:6a:1a:99:a8:4b:ee:5e:b4:26
# SHA1 Fingerprint: 2a:1d:60:27:d9:4a:b1:0a:1c:4d:91:5c:cd:33:a0:cb:3e:2d:54:cb
# SHA256 Fingerprint: 71:cc:a5:39:1f:9e:79:4b:04:80:25:30:b3:63:e1:21:da:8a:30:43:bb:26:66:2f:ea:4d:ca:7f:c9:51:a4:bd
-----BEGIN CERTIFICATE-----
MIICCjCCAZGgAwIBAgIQbkepyIuUtui7OyrYorLBmTAKBggqhkjOPQQDAzBHMQsw
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
MBIGA1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQA
IgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu
hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/l
xKvRHYqjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
DgQWBBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNnADBkAjBqUFJ0
CMRw3J5QdCHojXohw0+WbhXRIjVhLfoIN+4Zba3bssx9BzT1YBkstTTZbyACMANx
sbqjYAuG7ZoIapVon+Kz4ZNkfF6Tpt95LY2F45TPI11xzPKwTdb+mciUqXWi4w==
-----END CERTIFICATE-----
# Issuer: CN=UCA Global G2 Root O=UniTrust
# Subject: CN=UCA Global G2 Root O=UniTrust
# Label: "UCA Global G2 Root"
# Serial: 124779693093741543919145257850076631279
# MD5 Fingerprint: 80:fe:f0:c4:4a:f0:5c:62:32:9f:1c:ba:78:a9:50:f8
# SHA1 Fingerprint: 28:f9:78:16:19:7a:ff:18:25:18:aa:44:fe:c1:a0:ce:5c:b6:4c:8a
# SHA256 Fingerprint: 9b:ea:11:c9:76:fe:01:47:64:c1:be:56:a6:f9:14:b5:a5:60:31:7a:bd:99:88:39:33:82:e5:16:1a:a0:49:3c
-----BEGIN CERTIFICATE-----
MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9
MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBH
bG9iYWwgRzIgUm9vdDAeFw0xNjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0x
CzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEbMBkGA1UEAwwSVUNBIEds
b2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxeYr
b3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmToni9
kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzm
VHqUwCoV8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/R
VogvGjqNO7uCEeBHANBSh6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDc
C/Vkw85DvG1xudLeJ1uK6NjGruFZfc8oLTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIj
tm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/R+zvWr9LesGtOxdQXGLY
D0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBeKW4bHAyv
j5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6Dl
NaBa4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6
iIis7nCs+dwp4wwcOxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznP
O6Q0ibd5Ei9Hxeepl2n8pndntd978XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/
BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIHEjMz15DD/pQwIX4wV
ZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo5sOASD0Ee/oj
L3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5
1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl
1qnN3e92mI0ADs0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oU
b3n09tDh05S60FdRvScFDcH9yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LV
PtateJLbXDzz2K36uGt/xDYotgIVilQsnLAXc47QN6MUPJiVAAwpBVueSUmxX8fj
y88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHojhJi6IjMtX9Gl8Cb
EGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZkbxqg
DMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI
+Vg7RE+xygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGy
YiGqhkCyLmTTX8jjfhFnRR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bX
UB+K+wb1whnw0A==
-----END CERTIFICATE-----
# Issuer: CN=UCA Extended Validation Root O=UniTrust
# Subject: CN=UCA Extended Validation Root O=UniTrust
# Label: "UCA Extended Validation Root"
# Serial: 106100277556486529736699587978573607008
# MD5 Fingerprint: a1:f3:5f:43:c6:34:9b:da:bf:8c:7e:05:53:ad:96:e2
# SHA1 Fingerprint: a3:a1:b0:6f:24:61:23:4a:e3:36:a5:c2:37:fc:a6:ff:dd:f0:d7:3a
# SHA256 Fingerprint: d4:3a:f9:b3:54:73:75:5c:96:84:fc:06:d7:d8:cb:70:ee:5c:28:e7:73:fb:29:4e:b4:1e:e7:17:22:92:4d:24
-----BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBH
MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBF
eHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMx
MDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNV
BAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrsiWog
D4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvS
sPGP2KxFRv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aop
O2z6+I9tTcg1367r3CTueUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dk
sHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR59mzLC52LqGj3n5qiAno8geK+LLNEOfi
c0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH0mK1lTnj8/FtDw5lhIpj
VMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KRel7sFsLz
KuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/
TuDvB0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41G
sx2VYVdWf6/wFlthWG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs
1+lvK9JKBZP8nm9rZ/+I8U6laUpSNwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQD
fwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS3H5aBZ8eNJr34RQwDwYDVR0T
AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBADaN
l8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR
ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQ
VBcZEhrxH9cMaVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5
c6sq1WnIeJEmMX3ixzDx/BR4dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp
4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb+7lsq+KePRXBOy5nAliRn+/4Qh8s
t2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOWF3sGPjLtx7dCvHaj
2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwiGpWO
vpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2C
xR9GUeOcGMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmx
cmtpzyKEC2IPrNkZAJSidjzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbM
fjKaiJUINlK73nZfdklJrX+9ZSCyycErdhh2n1ax
-----END CERTIFICATE-----
# Issuer: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036
# Subject: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036
# Label: "Certigna Root CA"
# Serial: 269714418870597844693661054334862075617
# MD5 Fingerprint: 0e:5c:30:62:27:eb:5b:bc:d7:ae:62:ba:e9:d5:df:77
# SHA1 Fingerprint: 2d:0d:52:14:ff:9e:ad:99:24:01:74:20:47:6e:6c:85:27:27:f5:43
# SHA256 Fingerprint: d4:8d:3d:23:ee:db:50:a4:59:e5:51:97:60:1c:27:77:4b:9d:7b:18:c9:4d:5a:05:95:11:a1:02:50:b9:31:68
-----BEGIN CERTIFICATE-----
MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw
WjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw
MiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x
MzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjdaMFoxCzAJBgNVBAYTAkZSMRIwEAYD
VQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxGTAX
BgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
ggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sO
ty3tRQgXstmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9M
CiBtnyN6tMbaLOQdLNyzKNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPu
I9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8JXrJhFwLrN1CTivngqIkicuQstDuI7pm
TLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16XdG+RCYyKfHx9WzMfgIh
C59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq4NYKpkDf
ePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3Yz
IoejwpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWT
Co/1VTp2lc5ZmIoJlXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1k
JWumIWmbat10TWuXekG9qxf5kBdIjzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5
hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp//TBt2dzhauH8XwIDAQABo4IB
GjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE
FBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of
1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczov
L3d3d3cuY2VydGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilo
dHRwOi8vY3JsLmNlcnRpZ25hLmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYr
aHR0cDovL2NybC5kaGlteW90aXMuY29tL2NlcnRpZ25hcm9vdGNhLmNybDANBgkq
hkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOItOoldaDgvUSILSo3L
6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxPTGRG
HVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH6
0BGM+RFq7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncB
lA2c5uk5jR+mUYyZDDl34bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdi
o2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1
gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS6Cvu5zHbugRqh5jnxV/v
faci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaYtlu3zM63
Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh
jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw
3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0=
-----END CERTIFICATE-----
# Issuer: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI
# Subject: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI
# Label: "emSign Root CA - G1"
# Serial: 235931866688319308814040
# MD5 Fingerprint: 9c:42:84:57:dd:cb:0b:a7:2e:95:ad:b6:f3:da:bc:ac
# SHA1 Fingerprint: 8a:c7:ad:8f:73:ac:4e:c1:b5:75:4d:a5:40:f4:fc:cf:7c:b5:8e:8c
# SHA256 Fingerprint: 40:f6:af:03:46:a9:9a:a1:cd:1d:55:5a:4e:9c:ce:62:c7:f9:63:46:03:ee:40:66:15:83:3d:c8:c8:d0:03:67
-----BEGIN CERTIFICATE-----
MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD
VQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU
ZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH
MTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgxODMwMDBaMGcxCzAJBgNVBAYTAklO
MRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xv
Z2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQz
f2N4aLTNLnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO
8oG0x5ZOrRkVUkr+PHB1cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aq
d7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHWDV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhM
tTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ6DqS0hdW5TUaQBw+jSzt
Od9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrHhQIDAQAB
o0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQD
AgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31x
PaOfG1vR2vjTnGs2vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjM
wiI/aTvFthUvozXGaCocV685743QNcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6d
GNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q+Mri/Tm3R7nrft8EI6/6nAYH
6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeihU80Bv2noWgby
RQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx
iN66zB+Afko=
-----END CERTIFICATE-----
# Issuer: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI
# Subject: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI
# Label: "emSign ECC Root CA - G3"
# Serial: 287880440101571086945156
# MD5 Fingerprint: ce:0b:72:d1:9f:88:8e:d0:50:03:e8:e3:b8:8b:67:40
# SHA1 Fingerprint: 30:43:fa:4f:f2:57:dc:a0:c3:80:ee:2e:58:ea:78:b2:3f:e6:bb:c1
# SHA256 Fingerprint: 86:a1:ec:ba:08:9c:4a:8d:3b:be:27:34:c6:12:ba:34:1d:81:3e:04:3c:f9:e8:a8:62:cd:5c:57:a3:6b:be:6b
-----BEGIN CERTIFICATE-----
MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG
EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo
bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g
RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ
TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s
b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw
djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0
WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS
fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB
zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq
hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB
CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD
+JbNR6iC8hZVdyR+EhCVBCyj
-----END CERTIFICATE-----
# Issuer: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI
# Subject: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI
# Label: "emSign Root CA - C1"
# Serial: 825510296613316004955058
# MD5 Fingerprint: d8:e3:5d:01:21:fa:78:5a:b0:df:ba:d2:ee:2a:5f:68
# SHA1 Fingerprint: e7:2e:f1:df:fc:b2:09:28:cf:5d:d4:d5:67:37:b1:51:cb:86:4f:01
# SHA256 Fingerprint: 12:56:09:aa:30:1d:a0:a2:49:b9:7a:82:39:cb:6a:34:21:6f:44:dc:ac:9f:39:54:b1:42:92:f2:e8:c8:60:8f
-----BEGIN CERTIFICATE-----
MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkG
A1UEBhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEg
SW5jMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAw
MFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln
biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNpZ24gUm9v
dCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+upufGZ
BczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZ
HdPIWoU/Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH
3DspVpNqs8FqOp099cGXOFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvH
GPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4VI5b2P/AgNBbeCsbEBEV5f6f9vtKppa+c
xSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleoomslMuoaJuvimUnzYnu3Yy1
aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+XJGFehiq
TbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
BQADggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87
/kOXSTKZEhVb3xEp/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4
kqNPEjE2NuLe/gDEo2APJ62gsIq1NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrG
YQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9wC68AivTxEDkigcxHpvOJpkT
+xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQBmIMMMAVSKeo
WXzhriKi4gp6D/piq1JM4fHfyr6DDUI=
-----END CERTIFICATE-----
# Issuer: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI
# Subject: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI
# Label: "emSign ECC Root CA - C3"
# Serial: 582948710642506000014504
# MD5 Fingerprint: 3e:53:b3:a3:81:ee:d7:10:f8:d3:b0:1d:17:92:f5:d5
# SHA1 Fingerprint: b6:af:43:c2:9b:81:53:7d:f6:ef:6b:c3:1f:1f:60:15:0c:ee:48:66
# SHA256 Fingerprint: bc:4d:80:9b:15:18:9d:78:db:3e:1d:8c:f4:f9:72:6a:79:5d:a1:64:3c:a5:f1:35:8e:1d:db:0e:dc:0d:7e:b3
-----BEGIN CERTIFICATE-----
MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQG
EwJVUzETMBEGA1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMx
IDAeBgNVBAMTF2VtU2lnbiBFQ0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAw
MFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln
biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQDExdlbVNpZ24gRUND
IFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd6bci
MK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4Ojavti
sIGJAnB9SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0O
BBYEFPtaSNCAIEDyqOkAB2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB
Af8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQC02C8Cif22TGK6Q04ThHK1rt0c
3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwUZOR8loMRnLDRWmFLpg9J
0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ==
-----END CERTIFICATE-----
# Issuer: CN=Hongkong Post Root CA 3 O=Hongkong Post
# Subject: CN=Hongkong Post Root CA 3 O=Hongkong Post
# Label: "Hongkong Post Root CA 3"
# Serial: 46170865288971385588281144162979347873371282084
# MD5 Fingerprint: 11:fc:9f:bd:73:30:02:8a:fd:3f:f3:58:b9:cb:20:f0
# SHA1 Fingerprint: 58:a2:d0:ec:20:52:81:5b:c1:f3:f8:64:02:24:4e:c2:8e:02:4b:02
# SHA256 Fingerprint: 5a:2f:c0:3f:0c:83:b0:90:bb:fa:40:60:4b:09:88:44:6c:76:36:18:3d:f9:84:6e:17:10:1a:44:7f:b8:ef:d6
-----BEGIN CERTIFICATE-----
MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQEL
BQAwbzELMAkGA1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJ
SG9uZyBLb25nMRYwFAYDVQQKEw1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25n
a29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2MDMwMjI5NDZaFw00MjA2MDMwMjI5
NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtvbmcxEjAQBgNVBAcT
CUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMXSG9u
Z2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
AoICAQCziNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFO
dem1p+/l6TWZ5Mwc50tfjTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mI
VoBc+L0sPOFMV4i707mV78vH9toxdCim5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV
9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOesL4jpNrcyCse2m5FHomY
2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj0mRiikKY
vLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+Tt
bNe/JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZb
x39ri1UbSsUgYT2uy1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+
l2oBlKN8W4UdKjk60FSh0Tlxnf0h+bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YK
TE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsGxVd7GYYKecsAyVKvQv83j+Gj
Hno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwIDAQABo2MwYTAP
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e
i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEw
DQYJKoZIhvcNAQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG
7BJ8dNVI0lkUmcDrudHr9EgwW62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCk
MpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWldy8joRTnU+kLBEUx3XZL7av9YROXr
gZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov+BS5gLNdTaqX4fnk
GMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDceqFS
3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJm
Ozj/2ZQw9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+
l6mc1X5VTMbeRRAc6uk7nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6c
JfTzPV4e0hz5sy229zdcxsshTrD3mUcYhcErulWuBurQB7Lcq9CClnXO0lD+mefP
L5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB60PZ2Pierc+xYw5F9KBa
LJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fqdBb9HxEG
mpv0
-----END CERTIFICATE-----
-22
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@@ -8,30 +7,9 @@ certifi.py
This module returns the installation location of cacert.pem.
"""
import os
import warnings
class DeprecatedBundleWarning(DeprecationWarning):
"""
The weak security bundle is being deprecated. Please bother your service
provider to get them to stop using cross-signed roots.
"""
def where():
f = os.path.dirname(__file__)
return os.path.join(f, 'cacert.pem')
def old_where():
warnings.warn(
"The weak security bundle has been removed. certifi.old_where() is now an alias "
"of certifi.where(). Please update your code to use certifi.where() instead. "
"certifi.old_where() will be removed in 2018.",
DeprecatedBundleWarning
)
return where()
if __name__ == '__main__':
print(where())
@@ -0,0 +1,311 @@
import logging
import re
import sys
import ssl
from copy import deepcopy
from time import sleep
from collections import OrderedDict
from requests.sessions import Session
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.ssl_ import create_urllib3_context
from .interpreters import JavaScriptInterpreter
from .user_agent import User_Agent
try:
from requests_toolbelt.utils import dump
except ImportError:
pass
try:
import brotli
except ImportError:
pass
try:
from urlparse import urlparse
from urlparse import urlunparse
except ImportError:
from urllib.parse import urlparse
from urllib.parse import urlunparse
##########################################################################################################################################################
__version__ = '1.1.9'
BUG_REPORT = 'Cloudflare may have changed their technique, or there may be a bug in the script.'
##########################################################################################################################################################
class CipherSuiteAdapter(HTTPAdapter):
def __init__(self, cipherSuite=None, **kwargs):
self.cipherSuite = cipherSuite
if hasattr(ssl, 'PROTOCOL_TLS'):
self.ssl_context = create_urllib3_context(
ssl_version=getattr(ssl, 'PROTOCOL_TLSv1_3', ssl.PROTOCOL_TLSv1_2),
ciphers=self.cipherSuite
)
else:
self.ssl_context = create_urllib3_context(ssl_version=ssl.PROTOCOL_TLSv1)
super(CipherSuiteAdapter, self).__init__(**kwargs)
##########################################################################################################################################################
def init_poolmanager(self, *args, **kwargs):
kwargs['ssl_context'] = self.ssl_context
return super(CipherSuiteAdapter, self).init_poolmanager(*args, **kwargs)
##########################################################################################################################################################
def proxy_manager_for(self, *args, **kwargs):
kwargs['ssl_context'] = self.ssl_context
return super(CipherSuiteAdapter, self).proxy_manager_for(*args, **kwargs)
##########################################################################################################################################################
class CloudScraper(Session):
def __init__(self, *args, **kwargs):
self.debug = kwargs.pop('debug', False)
self.delay = kwargs.pop('delay', None)
self.interpreter = kwargs.pop('interpreter', 'js2py')
self.allow_brotli = kwargs.pop('allow_brotli', True if 'brotli' in sys.modules.keys() else False)
self.cipherSuite = None
super(CloudScraper, self).__init__(*args, **kwargs)
if 'requests' in self.headers['User-Agent']:
# Set a random User-Agent if no custom User-Agent has been set
self.headers = User_Agent(allow_brotli=self.allow_brotli).headers
self.mount('https://', CipherSuiteAdapter(self.loadCipherSuite()))
##########################################################################################################################################################
@staticmethod
def debugRequest(req):
try:
print(dump.dump_all(req).decode('utf-8'))
except: # noqa
pass
##########################################################################################################################################################
def loadCipherSuite(self):
if self.cipherSuite:
return self.cipherSuite
self.cipherSuite = ''
if hasattr(ssl, 'PROTOCOL_TLS'):
ciphers = [
'ECDHE-ECDSA-AES128-GCM-SHA256', 'ECDHE-RSA-AES128-GCM-SHA256', 'ECDHE-ECDSA-AES256-GCM-SHA384',
'ECDHE-RSA-AES256-GCM-SHA384', 'ECDHE-ECDSA-CHACHA20-POLY1305-SHA256', 'ECDHE-RSA-CHACHA20-POLY1305-SHA256',
'ECDHE-RSA-AES128-CBC-SHA', 'ECDHE-RSA-AES256-CBC-SHA', 'RSA-AES128-GCM-SHA256', 'RSA-AES256-GCM-SHA384',
'ECDHE-RSA-AES128-GCM-SHA256', 'RSA-AES256-SHA', '3DES-EDE-CBC'
]
if hasattr(ssl, 'PROTOCOL_TLSv1_3'):
ciphers.insert(0, ['GREASE_3A', 'GREASE_6A', 'AES128-GCM-SHA256', 'AES256-GCM-SHA256', 'AES256-GCM-SHA384', 'CHACHA20-POLY1305-SHA256'])
ctx = ssl.SSLContext(getattr(ssl, 'PROTOCOL_TLSv1_3', ssl.PROTOCOL_TLSv1_2))
for cipher in ciphers:
try:
ctx.set_ciphers(cipher)
self.cipherSuite = '{}:{}'.format(self.cipherSuite, cipher).rstrip(':')
except ssl.SSLError:
pass
return self.cipherSuite
##########################################################################################################################################################
def request(self, method, url, *args, **kwargs):
ourSuper = super(CloudScraper, self)
resp = ourSuper.request(method, url, *args, **kwargs)
if resp.headers.get('Content-Encoding') == 'br':
if self.allow_brotli and resp._content:
resp._content = brotli.decompress(resp.content)
else:
logging.warning('Brotli content detected, But option is disabled, we will not continue.')
return resp
# Debug request
if self.debug:
self.debugRequest(resp)
# Check if Cloudflare anti-bot is on
if self.isChallengeRequest(resp):
if resp.request.method != 'GET':
# Work around if the initial request is not a GET,
# Supersede with a GET then re-request the original METHOD.
self.request('GET', resp.url)
resp = ourSuper.request(method, url, *args, **kwargs)
else:
# Solve Challenge
resp = self.sendChallengeResponse(resp, **kwargs)
return resp
##########################################################################################################################################################
@staticmethod
def isChallengeRequest(resp):
if resp.headers.get('Server', '').startswith('cloudflare'):
if b'why_captcha' in resp.content or b'/cdn-cgi/l/chk_captcha' in resp.content:
raise ValueError('Captcha')
return (
resp.status_code in [429, 503]
and all(s in resp.content for s in [b'jschl_vc', b'jschl_answer'])
)
return False
##########################################################################################################################################################
def sendChallengeResponse(self, resp, **original_kwargs):
body = resp.text
# Cloudflare requires a delay before solving the challenge
if not self.delay:
try:
delay = float(re.search(r'submit\(\);\r?\n\s*},\s*([0-9]+)', body).group(1)) / float(1000)
if isinstance(delay, (int, float)):
self.delay = delay
except: # noqa
pass
sleep(self.delay)
parsed_url = urlparse(resp.url)
domain = parsed_url.netloc
submit_url = '{}://{}/cdn-cgi/l/chk_jschl'.format(parsed_url.scheme, domain)
cloudflare_kwargs = deepcopy(original_kwargs)
try:
params = OrderedDict()
s = re.search(r'name="s"\svalue="(?P<s_value>[^"]+)', body)
if s:
params['s'] = s.group('s_value')
params.update(
[
('jschl_vc', re.search(r'name="jschl_vc" value="(\w+)"', body).group(1)),
('pass', re.search(r'name="pass" value="(.+?)"', body).group(1))
]
)
params = cloudflare_kwargs.setdefault('params', params)
except Exception as e:
raise ValueError('Unable to parse Cloudflare anti-bots page: {} {}'.format(e.message, BUG_REPORT))
# Solve the Javascript challenge
params['jschl_answer'] = JavaScriptInterpreter.dynamicImport(self.interpreter).solveChallenge(body, domain)
# Requests transforms any request into a GET after a redirect,
# so the redirect has to be handled manually here to allow for
# performing other types of requests even as the first request.
cloudflare_kwargs['allow_redirects'] = False
redirect = self.request(resp.request.method, submit_url, **cloudflare_kwargs)
redirect_location = urlparse(redirect.headers['Location'])
if not redirect_location.netloc:
redirect_url = urlunparse(
(
parsed_url.scheme,
domain,
redirect_location.path,
redirect_location.params,
redirect_location.query,
redirect_location.fragment
)
)
return self.request(resp.request.method, redirect_url, **original_kwargs)
return self.request(resp.request.method, redirect.headers['Location'], **original_kwargs)
##########################################################################################################################################################
@classmethod
def create_scraper(cls, sess=None, **kwargs):
"""
Convenience function for creating a ready-to-go CloudScraper object.
"""
scraper = cls(**kwargs)
if sess:
attrs = ['auth', 'cert', 'cookies', 'headers', 'hooks', 'params', 'proxies', 'data']
for attr in attrs:
val = getattr(sess, attr, None)
if val:
setattr(scraper, attr, val)
return scraper
##########################################################################################################################################################
# Functions for integrating cloudscraper with other applications and scripts
@classmethod
def get_tokens(cls, url, **kwargs):
scraper = cls.create_scraper(
debug=kwargs.pop('debug', False),
delay=kwargs.pop('delay', None),
interpreter=kwargs.pop('interpreter', 'js2py'),
allow_brotli=kwargs.pop('allow_brotli', True),
)
try:
resp = scraper.get(url, **kwargs)
resp.raise_for_status()
except Exception:
logging.error('"{}" returned an error. Could not collect tokens.'.format(url))
raise
domain = urlparse(resp.url).netloc
# noinspection PyUnusedLocal
cookie_domain = None
for d in scraper.cookies.list_domains():
if d.startswith('.') and d in ('.{}'.format(domain)):
cookie_domain = d
break
else:
raise ValueError('Unable to find Cloudflare cookies. Does the site actually have Cloudflare IUAM ("I\'m Under Attack Mode") enabled?')
return (
{
'__cfduid': scraper.cookies.get('__cfduid', '', domain=cookie_domain),
'cf_clearance': scraper.cookies.get('cf_clearance', '', domain=cookie_domain)
},
scraper.headers['User-Agent']
)
##########################################################################################################################################################
@classmethod
def get_cookie_string(cls, url, **kwargs):
"""
Convenience function for building a Cookie HTTP header value.
"""
tokens, user_agent = cls.get_tokens(url, **kwargs)
return '; '.join('='.join(pair) for pair in tokens.items()), user_agent
##########################################################################################################################################################
create_scraper = CloudScraper.create_scraper
get_tokens = CloudScraper.get_tokens
get_cookie_string = CloudScraper.get_cookie_string
@@ -0,0 +1,89 @@
import re
import sys
import logging
import abc
if sys.version_info >= (3, 4):
ABC = abc.ABC # noqa
else:
ABC = abc.ABCMeta('ABC', (), {})
##########################################################################################################################################################
BUG_REPORT = 'Cloudflare may have changed their technique, or there may be a bug in the script.'
##########################################################################################################################################################
interpreters = {}
class JavaScriptInterpreter(ABC):
@abc.abstractmethod
def __init__(self, name):
interpreters[name] = self
@classmethod
def dynamicImport(cls, name):
if name not in interpreters:
try:
__import__('{}.{}'.format(cls.__module__, name))
if not isinstance(interpreters.get(name), JavaScriptInterpreter):
raise ImportError('The interpreter was not initialized.')
except ImportError:
logging.error('Unable to load {} interpreter'.format(name))
raise
return interpreters[name]
@abc.abstractmethod
def eval(self, jsEnv, js):
pass
def solveChallenge(self, body, domain):
try:
js = re.search(
r'setTimeout\(function\(\){\s+(var s,t,o,p,b,r,e,a,k,i,n,g,f.+?\r?\n[\s\S]+?a\.value =.+?)\r?\n',
body
).group(1)
except Exception:
raise ValueError('Unable to identify Cloudflare IUAM Javascript on website. {}'.format(BUG_REPORT))
js = re.sub(r'\s{2,}', ' ', js, flags=re.MULTILINE | re.DOTALL).replace('\'; 121\'', '')
js += '\na.value;'
jsEnv = '''
String.prototype.italics=function(str) {{return "<i>" + this + "</i>";}};
var document = {{
createElement: function () {{
return {{ firstChild: {{ href: "https://{domain}/" }} }}
}},
getElementById: function () {{
return {{"innerHTML": "{innerHTML}"}};
}}
}};
'''
try:
innerHTML = re.search(
r'<div(?: [^<>]*)? id="([^<>]*?)">([^<>]*?)</div>',
body,
re.MULTILINE | re.DOTALL
)
innerHTML = innerHTML.group(2) if innerHTML else ''
except: # noqa
logging.error('Error extracting Cloudflare IUAM Javascript. {}'.format(BUG_REPORT))
raise
try:
result = self.eval(
re.sub(r'\s{2,}', ' ', jsEnv.format(domain=domain, innerHTML=innerHTML), flags=re.MULTILINE | re.DOTALL),
js
)
float(result)
except Exception:
logging.error('Error executing Cloudflare IUAM Javascript. {}'.format(BUG_REPORT))
raise
return result
@@ -0,0 +1,32 @@
from __future__ import absolute_import
import js2py
import logging
import base64
from . import JavaScriptInterpreter
from .jsunfuck import jsunfuck
class ChallengeInterpreter(JavaScriptInterpreter):
def __init__(self):
super(ChallengeInterpreter, self).__init__('js2py')
def eval(self, jsEnv, js):
if js2py.eval_js('(+(+!+[]+[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+[!+[]+!+[]]+[+[]])+[])[+!+[]]') == '1':
logging.warning('WARNING - Please upgrade your js2py https://github.com/PiotrDabkowski/Js2Py, applying work around for the meantime.')
js = jsunfuck(js)
def atob(s):
return base64.b64decode('{}'.format(s)).decode('utf-8')
js2py.disable_pyimport()
context = js2py.EvalJs({'atob': atob})
result = context.eval('{}{}'.format(jsEnv, js))
return result
ChallengeInterpreter()
@@ -0,0 +1,97 @@
MAPPING = {
'a': '(false+"")[1]',
'b': '([]["entries"]()+"")[2]',
'c': '([]["fill"]+"")[3]',
'd': '(undefined+"")[2]',
'e': '(true+"")[3]',
'f': '(false+"")[0]',
'g': '(false+[0]+String)[20]',
'h': '(+(101))["to"+String["name"]](21)[1]',
'i': '([false]+undefined)[10]',
'j': '([]["entries"]()+"")[3]',
'k': '(+(20))["to"+String["name"]](21)',
'l': '(false+"")[2]',
'm': '(Number+"")[11]',
'n': '(undefined+"")[1]',
'o': '(true+[]["fill"])[10]',
'p': '(+(211))["to"+String["name"]](31)[1]',
'q': '(+(212))["to"+String["name"]](31)[1]',
'r': '(true+"")[1]',
's': '(false+"")[3]',
't': '(true+"")[0]',
'u': '(undefined+"")[0]',
'v': '(+(31))["to"+String["name"]](32)',
'w': '(+(32))["to"+String["name"]](33)',
'x': '(+(101))["to"+String["name"]](34)[1]',
'y': '(NaN+[Infinity])[10]',
'z': '(+(35))["to"+String["name"]](36)',
'A': '(+[]+Array)[10]',
'B': '(+[]+Boolean)[10]',
'C': 'Function("return escape")()(("")["italics"]())[2]',
'D': 'Function("return escape")()([]["fill"])["slice"]("-1")',
'E': '(RegExp+"")[12]',
'F': '(+[]+Function)[10]',
'G': '(false+Function("return Date")()())[30]',
'I': '(Infinity+"")[0]',
'M': '(true+Function("return Date")()())[30]',
'N': '(NaN+"")[0]',
'O': '(NaN+Function("return{}")())[11]',
'R': '(+[]+RegExp)[10]',
'S': '(+[]+String)[10]',
'T': '(NaN+Function("return Date")()())[30]',
'U': '(NaN+Function("return{}")()["to"+String["name"]]["call"]())[11]',
' ': '(NaN+[]["fill"])[11]',
'"': '("")["fontcolor"]()[12]',
'%': 'Function("return escape")()([]["fill"])[21]',
'&': '("")["link"](0+")[10]',
'(': '(undefined+[]["fill"])[22]',
')': '([0]+false+[]["fill"])[20]',
'+': '(+(+!+[]+(!+[]+[])[!+[]+!+[]+!+[]]+[+!+[]]+[+[]]+[+[]])+[])[2]',
',': '([]["slice"]["call"](false+"")+"")[1]',
'-': '(+(.+[0000000001])+"")[2]',
'.': '(+(+!+[]+[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+[!+[]+!+[]]+[+[]])+[])[+!+[]]',
'/': '(false+[0])["italics"]()[10]',
':': '(RegExp()+"")[3]',
';': '("")["link"](")[14]',
'<': '("")["italics"]()[0]',
'=': '("")["fontcolor"]()[11]',
'>': '("")["italics"]()[2]',
'?': '(RegExp()+"")[2]',
'[': '([]["entries"]()+"")[0]',
']': '([]["entries"]()+"")[22]',
'{': '(true+[]["fill"])[20]',
'}': '([]["fill"]+"")["slice"]("-1")'
}
SIMPLE = {
'false': '![]',
'true': '!![]',
'undefined': '[][[]]',
'NaN': '+[![]]',
'Infinity': '+(+!+[]+(!+[]+[])[!+[]+!+[]+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]])' # +"1e1000"
}
CONSTRUCTORS = {
'Array': '[]',
'Number': '(+[])',
'String': '([]+[])',
'Boolean': '(![])',
'Function': '[]["fill"]',
'RegExp': 'Function("return/"+false+"/")()'
}
def jsunfuck(jsfuckString):
for key in sorted(MAPPING, key=lambda k: len(MAPPING[k]), reverse=True):
if MAPPING.get(key) in jsfuckString:
jsfuckString = jsfuckString.replace(MAPPING.get(key), '"{}"'.format(key))
for key in sorted(SIMPLE, key=lambda k: len(SIMPLE[k]), reverse=True):
if SIMPLE.get(key) in jsfuckString:
jsfuckString = jsfuckString.replace(SIMPLE.get(key), '{}'.format(key))
# for key in sorted(CONSTRUCTORS, key=lambda k: len(CONSTRUCTORS[k]), reverse=True):
# if CONSTRUCTORS.get(key) in jsfuckString:
# jsfuckString = jsfuckString.replace(CONSTRUCTORS.get(key), '{}'.format(key))
return jsfuckString
@@ -0,0 +1,46 @@
import base64
import logging
import subprocess
from . import JavaScriptInterpreter
##########################################################################################################################################################
BUG_REPORT = 'Cloudflare may have changed their technique, or there may be a bug in the script.'
##########################################################################################################################################################
class ChallengeInterpreter(JavaScriptInterpreter):
def __init__(self):
super(ChallengeInterpreter, self).__init__('nodejs')
def eval(self, jsEnv, js):
try:
js = 'var atob = function(str) {return Buffer.from(str, "base64").toString("binary");};' \
'var challenge = atob("%s");' \
'var context = {atob: atob};' \
'var options = {filename: "iuam-challenge.js", timeout: 4000};' \
'var answer = require("vm").runInNewContext(challenge, context, options);' \
'process.stdout.write(String(answer));' \
% base64.b64encode('{}{}'.format(jsEnv, js).encode('UTF-8')).decode('ascii')
return subprocess.check_output(['node', '-e', js])
except OSError as e:
if e.errno == 2:
raise EnvironmentError(
'Missing Node.js runtime. Node is required and must be in the PATH (check with `node -v`). Your Node binary may be called `nodejs` rather than `node`, '
'in which case you may need to run `apt-get install nodejs-legacy` on some Debian-based systems. (Please read the cloudscraper'
' README\'s Dependencies section: https://github.com/VeNoMouS/cloudscraper#dependencies.'
)
raise
except Exception:
logging.error('Error executing Cloudflare IUAM Javascript. %s' % BUG_REPORT)
raise
pass
ChallengeInterpreter()
@@ -0,0 +1,40 @@
import os
import json
import random
import logging
from collections import OrderedDict
##########################################################################################################################################################
class User_Agent():
##########################################################################################################################################################
def __init__(self, *args, **kwargs):
self.headers = None
self.loadUserAgent(*args, **kwargs)
##########################################################################################################################################################
def loadUserAgent(self, *args, **kwargs):
browser = kwargs.pop('browser', 'chrome')
user_agents = json.load(
open(os.path.join(os.path.dirname(__file__), 'browsers.json'), 'r'),
object_pairs_hook=OrderedDict
)
if not user_agents.get(browser):
logging.error('Sorry "{}" browser User-Agent was not found.'.format(browser))
raise
user_agent = random.choice(user_agents.get(browser))
self.headers = user_agent.get('headers')
self.headers['User-Agent'] = random.choice(user_agent.get('User-Agent'))
if not kwargs.get('allow_brotli', False):
if 'br' in self.headers['Accept-Encoding']:
self.headers['Accept-Encoding'] = ','.join([encoding for encoding in self.headers['Accept-Encoding'].split(',') if encoding.strip() != 'br']).strip()
@@ -0,0 +1,336 @@
{
"chrome": [
{
"User-Agent": [
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.101 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.101 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.101 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.75 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.110 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.113 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.89 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.89 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.89 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.89 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.89 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.89 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.76 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.76 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.76 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.76 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.76 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.76 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36"
],
"headers": {
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1",
"User-Agent": null,
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.8",
"Accept-Encoding": "gzip, deflate, , br"
}
},
{
"User-Agent": [
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.86 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.78 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36"
],
"headers": {
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1",
"User-Agent": null,
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.8",
"Accept-Encoding": "gzip, deflate, br"
}
},
{
"User-Agent": [
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.62 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.67 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.62 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.75 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.170 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.75 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.81 Safari/537.36"
],
"headers": {
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1",
"User-Agent": null,
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br"
}
},
{
"User-Agent": [
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36"
],
"headers": {
"Connection": "keep-alive",
"User-Agent": null,
"Upgrade-Insecure-Requests": "1",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br"
}
},
{
"User-Agent": [
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.40 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.40 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.28 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.28 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.28 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.28 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.28 Safari/537.36"
],
"headers": {
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1",
"User-Agent": null,
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br"
}
},
{
"User-Agent": [
"Mozilla/5.0 (Linux; Android 8.1.0; SM-N960F Build/M1AJQ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 8.0.0; SM-G965F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 Build/OPD1.170816.010) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 8.0.0; Pixel Build/OPR6.170623.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.1.1; SM-A530F Build/NMF26X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.1; Pixel Build/NDE63H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; SM-G955F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; SM-G950F Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.0; SM-T825 Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0.1; SM-G930F Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0; Nexus 6 Build/MRA58K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0; XT1092 Build/MPE24.49-18) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 6.0.1; SM-N910C Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 5.0.2; SM-G920F Build/LRX22G) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 5.0; Nexus 6 Build/LRX21O) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 9; Pixel 3 XL Build/PD1A.180720.030) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 9; Pixel 3 Build/PD1A.180720.030) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 9; Pixel 2 Build/PPR1.180610.009) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 4.4; Nexus 5 Build/KRT16M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 4.4.2; SM-T530 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Safari/537.36",
"Mozilla/5.0 (Linux; Android 4.4.4; SM-N910C Build/KTU84P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 5.1.1; Nexus 9 Build/LMY47X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Safari/537.36",
"Mozilla/5.0 (Linux; Android 7.1.1; SM-N950F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.90 Mobile Safari/537.36"
],
"headers": {
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1",
"User-Agent": null,
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "en-US,en;q=0.9"
}
},
{
"User-Agent": [
"Mozilla/5.0 (Linux; Android 8.1.0; SM-T835 Build/M1AJQ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Safari/537.36",
"Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36",
"Mozilla/5.0 (Linux; Android 5.0; XT1092 Build/LXE22.46-19) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36"
],
"headers": {
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1",
"User-Agent": null,
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8"
}
}
]
}
+516
View File
@@ -0,0 +1,516 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""Death by Captcha HTTP and socket API clients.
There are two types of Death by Captcha (DBC hereinafter) API: HTTP and
socket ones. Both offer the same functionalily, with the socket API
sporting faster responses and using way less connections.
To access the socket API, use SocketClient class; for the HTTP API, use
HttpClient class. Both are thread-safe. SocketClient keeps a persistent
connection opened and serializes all API requests sent through it, thus
it is advised to keep a pool of them if you're script is heavily
multithreaded.
Both SocketClient and HttpClient give you the following methods:
get_user()
Returns your DBC account details as a dict with the following keys:
"user": your account numeric ID; if login fails, it will be the only
item with the value of 0;
"rate": your CAPTCHA rate, i.e. how much you will be charged for one
solved CAPTCHA in US cents;
"balance": your DBC account balance in US cents;
"is_banned": flag indicating whether your account is suspended or not.
get_balance()
Returns your DBC account balance in US cents.
get_captcha(cid)
Returns an uploaded CAPTCHA details as a dict with the following keys:
"captcha": the CAPTCHA numeric ID; if no such CAPTCHAs found, it will
be the only item with the value of 0;
"text": the CAPTCHA text, if solved, otherwise None;
"is_correct": flag indicating whether the CAPTCHA was solved correctly
(DBC can detect that in rare cases).
The only argument `cid` is the CAPTCHA numeric ID.
get_text(cid)
Returns an uploaded CAPTCHA text (None if not solved). The only argument
`cid` is the CAPTCHA numeric ID.
report(cid)
Reports an incorrectly solved CAPTCHA. The only argument `cid` is the
CAPTCHA numeric ID. Returns True on success, False otherwise.
upload(captcha)
Uploads a CAPTCHA. The only argument `captcha` can be either file-like
object (any object with `read` method defined, actually, so StringIO
will do), or CAPTCHA image file name. On successul upload you'll get
the CAPTCHA details dict (see get_captcha() method).
NOTE: AT THIS POINT THE UPLOADED CAPTCHA IS NOT SOLVED YET! You have
to poll for its status periodically using get_captcha() or get_text()
method until the CAPTCHA is solved and you get the text.
decode(captcha, timeout=DEFAULT_TIMEOUT)
A convenient method that uploads a CAPTCHA and polls for its status
periodically, but no longer than `timeout` (defaults to 60 seconds).
If solved, you'll get the CAPTCHA details dict (see get_captcha()
method for details). See upload() method for details on `captcha`
argument.
Visit http://www.deathbycaptcha.com/user/api for updates.
"""
import base64
import binascii
import errno
import imghdr
import random
import os
import select
import socket
import sys
import threading
import time
import urllib
import urllib2
try:
from json import read as json_decode, write as json_encode
except ImportError:
try:
from json import loads as json_decode, dumps as json_encode
except ImportError:
from simplejson import loads as json_decode, dumps as json_encode
# API version and unique software ID
API_VERSION = 'DBC/Python v4.6'
# Default CAPTCHA timeout and decode() polling interval
DEFAULT_TIMEOUT = 60
DEFAULT_TOKEN_TIMEOUT = 120
POLLS_INTERVAL = [1, 1, 2, 3, 2, 2, 3, 2, 2]
DFLT_POLL_INTERVAL = 3
# Base HTTP API url
HTTP_BASE_URL = 'http://api.dbcapi.me/api'
# Preferred HTTP API server's response content type, do not change
HTTP_RESPONSE_TYPE = 'application/json'
# Socket API server's host & ports range
SOCKET_HOST = 'api.dbcapi.me'
SOCKET_PORTS = range(8123, 8131)
def _load_image(captcha):
if hasattr(captcha, 'read'):
img = captcha.read()
elif type(captcha) == bytearray:
img = captcha
else:
img = ''
try:
captcha_file = open(captcha, 'rb')
except Exception:
raise
else:
img = captcha_file.read()
captcha_file.close()
if not len(img):
raise ValueError('CAPTCHA image is empty')
elif imghdr.what(None, img) is None:
raise TypeError('Unknown CAPTCHA image type')
else:
return img
class AccessDeniedException(Exception):
pass
class Client(object):
"""Death by Captcha API Client."""
def __init__(self, username, password):
self.is_verbose = False
self.userpwd = {'username': username, 'password': password}
def _log(self, cmd, msg=''):
if self.is_verbose:
print '%d %s %s' % (time.time(), cmd, msg.rstrip())
return self
def close(self):
pass
def connect(self):
pass
def get_user(self):
"""Fetch user details -- ID, balance, rate and banned status."""
raise NotImplementedError()
def get_balance(self):
"""Fetch user balance (in US cents)."""
return self.get_user().get('balance')
def get_captcha(self, cid):
"""Fetch a CAPTCHA details -- ID, text and correctness flag."""
raise NotImplementedError()
def get_text(self, cid):
"""Fetch a CAPTCHA text."""
return self.get_captcha(cid).get('text') or None
def report(self, cid):
"""Report a CAPTCHA as incorrectly solved."""
raise NotImplementedError()
def upload(self, captcha):
"""Upload a CAPTCHA.
Accepts file names and file-like objects. Returns CAPTCHA details
dict on success.
"""
raise NotImplementedError()
def decode(self, captcha=None, timeout=None, **kwargs):
"""
Try to solve a CAPTCHA.
See Client.upload() for arguments details.
Uploads a CAPTCHA, polls for its status periodically with arbitrary
timeout (in seconds), returns CAPTCHA details if (correctly) solved.
"""
if not timeout:
if not captcha:
timeout = DEFAULT_TOKEN_TIMEOUT
else:
timeout = DEFAULT_TIMEOUT
deadline = time.time() + (max(0, timeout) or DEFAULT_TIMEOUT)
uploaded_captcha = self.upload(captcha, **kwargs)
if uploaded_captcha:
intvl_idx = 0 # POLL_INTERVAL index
while deadline > time.time() and not uploaded_captcha.get('text'):
intvl, intvl_idx = self._get_poll_interval(intvl_idx)
time.sleep(intvl)
pulled = self.get_captcha(uploaded_captcha['captcha'])
if pulled['captcha'] == uploaded_captcha['captcha']:
uploaded_captcha = pulled
if uploaded_captcha.get('text') and \
uploaded_captcha.get('is_correct'):
return uploaded_captcha
def _get_poll_interval(self, idx):
"""Returns poll interval and next index depending on index provided"""
if len(POLLS_INTERVAL) > idx:
intvl = POLLS_INTERVAL[idx]
else:
intvl = DFLT_POLL_INTERVAL
idx += 1
return intvl, idx
class HttpClient(Client):
"""Death by Captcha HTTP API client."""
def __init__(self, *args):
Client.__init__(self, *args)
self.opener = urllib2.build_opener(urllib2.HTTPRedirectHandler())
def _call(self, cmd, payload=None, headers=None):
if headers is None:
headers = {}
headers['Accept'] = HTTP_RESPONSE_TYPE
headers['User-Agent'] = API_VERSION
if hasattr(payload, 'items'):
payload = urllib.urlencode(payload)
self._log('SEND', '%s %d %s' % (cmd, len(payload), payload))
else:
self._log('SEND', '%s' % cmd)
if payload is not None:
headers['Content-Length'] = len(payload)
try:
response = self.opener.open(urllib2.Request(
HTTP_BASE_URL + '/' + cmd.strip('/'),
data=payload,
headers=headers
)).read()
except urllib2.HTTPError, err:
if 403 == err.code:
raise AccessDeniedException('Access denied, please check'
' your credentials and/or balance')
elif 400 == err.code or 413 == err.code:
raise ValueError("CAPTCHA was rejected by the service, check"
" if it's a valid image")
elif 503 == err.code:
raise OverflowError("CAPTCHA was rejected due to service"
" overload, try again later")
else:
raise err
else:
self._log('RECV', '%d %s' % (len(response), response))
try:
return json_decode(response)
except Exception:
raise RuntimeError('Invalid API response')
return {}
def get_user(self):
return self._call('user', self.userpwd.copy()) or {'user': 0}
def get_captcha(self, cid):
return self._call('captcha/%d' % cid) or {'captcha': 0}
def report(self, cid):
return not self._call('captcha/%d/report' % cid,
self.userpwd.copy()).get('is_correct')
def upload(self, captcha=None, **kwargs):
boundary = binascii.hexlify(os.urandom(16))
banner = kwargs.get('banner', '')
if banner:
kwargs['banner'] = 'base64:' + base64.b64encode(_load_image(banner))
body = '\r\n'.join(('\r\n'.join((
'--%s' % boundary,
'Content-Disposition: form-data; name="%s"' % k,
'Content-Type: text/plain',
'Content-Length: %d' % len(str(v)),
'',
str(v)
))) for k, v in self.userpwd.items())
body += '\r\n'.join(('\r\n'.join((
'--%s' % boundary,
'Content-Disposition: form-data; name="%s"' % k,
'Content-Type: text/plain',
'Content-Length: %d' % len(str(v)),
'',
str(v)
))) for k, v in kwargs.items())
if captcha:
img = _load_image(captcha)
body += '\r\n'.join((
'',
'--%s' % boundary,
'Content-Disposition: form-data; name="captchafile"; '
'filename="captcha"',
'Content-Type: application/octet-stream',
'Content-Length: %d' % len(img),
'',
img,
'--%s--' % boundary,
''
))
response = self._call('captcha', body, {
'Content-Type': 'multipart/form-data; boundary="%s"' % boundary
}) or {}
if response.get('captcha'):
return response
class SocketClient(Client):
"""Death by Captcha socket API client."""
TERMINATOR = '\r\n'
def __init__(self, *args):
Client.__init__(self, *args)
self.socket_lock = threading.Lock()
self.socket = None
def close(self):
if self.socket:
self._log('CLOSE')
try:
self.socket.shutdown(socket.SHUT_RDWR)
except socket.error:
pass
finally:
self.socket.close()
self.socket = None
def connect(self):
if not self.socket:
self._log('CONN')
host = (socket.gethostbyname(SOCKET_HOST),
random.choice(SOCKET_PORTS))
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.settimeout(0)
try:
self.socket.connect(host)
except socket.error, err:
if (err.args[0] not in
(errno.EAGAIN, errno.EWOULDBLOCK, errno.EINPROGRESS)):
self.close()
raise err
return self.socket
def __del__(self):
self.close()
def _sendrecv(self, sock, buf):
self._log('SEND', buf)
fds = [sock]
buf += self.TERMINATOR
response = ''
intvl_idx = 0
while True:
intvl, intvl_idx = self._get_poll_interval(intvl_idx)
rds, wrs, exs = select.select((not buf and fds) or [],
(buf and fds) or [],
fds,
intvl)
if exs:
raise IOError('select() failed')
try:
if wrs:
while buf:
buf = buf[wrs[0].send(buf):]
elif rds:
while True:
s = rds[0].recv(256)
if not s:
raise IOError('recv(): connection lost')
else:
response += s
except socket.error, err:
if (err.args[0] not in
(errno.EAGAIN, errno.EWOULDBLOCK, errno.EINPROGRESS)):
raise err
if response.endswith(self.TERMINATOR):
self._log('RECV', response)
return response.rstrip(self.TERMINATOR)
raise IOError('send/recv timed out')
def _call(self, cmd, data=None):
if data is None:
data = {}
data['cmd'] = cmd
data['version'] = API_VERSION
request = json_encode(data)
response = None
for _ in range(2):
if not self.socket and cmd != 'login':
self._call('login', self.userpwd.copy())
self.socket_lock.acquire()
try:
sock = self.connect()
response = self._sendrecv(sock, request)
except IOError, err:
sys.stderr.write(str(err) + "\n")
self.close()
except socket.error, err:
sys.stderr.write(str(err) + "\n")
self.close()
raise IOError('Connection refused')
else:
break
finally:
self.socket_lock.release()
if response is None:
raise IOError('Connection lost or timed out during API request')
try:
response = json_decode(response)
except Exception:
raise RuntimeError('Invalid API response')
if not response.get('error'):
return response
error = response['error']
if error in ('not-logged-in', 'invalid-credentials'):
raise AccessDeniedException('Access denied, check your credentials')
elif 'banned' == error:
raise AccessDeniedException('Access denied, account is suspended')
elif 'insufficient-funds' == error:
raise AccessDeniedException(
'CAPTCHA was rejected due to low balance')
elif 'invalid-captcha' == error:
raise ValueError('CAPTCHA is not a valid image')
elif 'service-overload' == error:
raise OverflowError(
'CAPTCHA was rejected due to service overload, try again later')
else:
self.socket_lock.acquire()
self.close()
self.socket_lock.release()
raise RuntimeError('API server error occured: %s' % error)
def get_user(self):
return self._call('user') or {'user': 0}
def get_captcha(self, cid):
return self._call('captcha', {'captcha': cid}) or {'captcha': 0}
def upload(self, captcha=None, **kwargs):
data = {}
if captcha:
data['captcha'] = base64.b64encode(_load_image(captcha))
if kwargs:
banner = kwargs.get('banner', '')
if banner:
kwargs['banner'] = base64.b64encode(_load_image(banner))
data.update(kwargs)
response = self._call('upload', data)
if response.get('captcha'):
uploaded_captcha = dict(
(k, response.get(k))
for k in ('captcha', 'text', 'is_correct')
)
if not uploaded_captcha['text']:
uploaded_captcha['text'] = None
return uploaded_captcha
def report(self, cid):
return not self._call('report', {'captcha': cid}).get('is_correct')
if '__main__' == __name__:
# Put your DBC username & password here:
# client = HttpClient(sys.argv[1], sys.argv[2])
client = SocketClient(sys.argv[1], sys.argv[2])
client.is_verbose = True
print 'Your balance is %s US cents' % client.get_balance()
for fn in sys.argv[3:]:
try:
# Put your CAPTCHA image file name or file-like object, and optional
# solving timeout (in seconds) here:
captcha = client.decode(fn, DEFAULT_TIMEOUT)
except Exception, e:
sys.stderr.write('Failed uploading CAPTCHA: %s\n' % (e, ))
captcha = None
if captcha:
print 'CAPTCHA %d solved: %s' % \
(captcha['captcha'], captcha['text'])
# Report as incorrectly solved if needed. Make sure the CAPTCHA was
# in fact incorrectly solved!
# try:
# client.report(captcha['captcha'])
# except Exception, e:
# sys.stderr.write('Failed reporting CAPTCHA: %s\n' % (e, ))
+41 -14
View File
@@ -5,6 +5,7 @@ import pickle
import shutil
import tempfile
import traceback
import hashlib
import appdirs
@@ -89,7 +90,7 @@ class FileCache(MutableMapping):
"""
def __init__(self, appname, flag='c', mode=0o666, keyencoding='utf-8',
serialize=True, app_cache_dir=None):
serialize=True, app_cache_dir=None, key_file_ext=".txt"):
"""Initialize a :class:`FileCache` object."""
if not isinstance(flag, str):
raise TypeError("flag must be str not '{}'".format(type(flag)))
@@ -130,6 +131,7 @@ class FileCache(MutableMapping):
self._mode = mode
self._keyencoding = keyencoding
self._serialize = serialize
self.key_file_ext = key_file_ext
def _parse_appname(self, appname):
"""Splits an appname into the appname and subcache components."""
@@ -188,6 +190,11 @@ class FileCache(MutableMapping):
except:
logger.error("Couldn't write content from %r to cache file: %r: %s", ekey, filename,
traceback.format_exc())
try:
self.__write_to_file(filename + self.key_file_ext, ekey)
except:
logger.error("Couldn't write content from %r to cache file: %r: %s", ekey, filename,
traceback.format_exc())
self._buffer.clear()
self._sync = False
@@ -196,8 +203,7 @@ class FileCache(MutableMapping):
raise ValueError("invalid operation on closed cache")
def _encode_key(self, key):
"""Encode key using *hex_codec* for constructing a cache filename.
"""
Keys are implicitly converted to :class:`bytes` if passed as
:class:`str`.
@@ -206,16 +212,15 @@ class FileCache(MutableMapping):
key = key.encode(self._keyencoding)
elif not isinstance(key, bytes):
raise TypeError("key must be bytes or str")
return codecs.encode(key, 'hex_codec').decode(self._keyencoding)
return key.decode(self._keyencoding)
def _decode_key(self, key):
"""Decode key using hex_codec to retrieve the original key.
"""
Keys are returned as :class:`str` if serialization is enabled.
Keys are returned as :class:`bytes` if serialization is disabled.
"""
bkey = codecs.decode(key.encode(self._keyencoding), 'hex_codec')
bkey = key.encode(self._keyencoding)
return bkey.decode(self._keyencoding) if self._serialize else bkey
def _dumps(self, value):
@@ -226,18 +231,24 @@ class FileCache(MutableMapping):
def _key_to_filename(self, key):
"""Convert an encoded key to an absolute cache filename."""
return os.path.join(self.cache_dir, key)
if isinstance(key, unicode):
key = key.encode(self._keyencoding)
return os.path.join(self.cache_dir, hashlib.md5(key).hexdigest())
def _filename_to_key(self, absfilename):
"""Convert an absolute cache filename to a key name."""
return os.path.split(absfilename)[1]
hkey_hdr_fn = absfilename + self.key_file_ext
if os.path.isfile(hkey_hdr_fn):
with open(hkey_hdr_fn, 'rb') as f:
key = f.read()
return key.decode(self._keyencoding) if self._serialize else key
def _all_filenames(self, scandir_generic=True):
"""Return a list of absolute cache filenames"""
_scandir = _scandir_generic if scandir_generic else scandir
try:
for entry in _scandir(self.cache_dir):
if entry.is_file(follow_symlinks=False):
if entry.is_file(follow_symlinks=False) and not entry.name.endswith(self.key_file_ext):
yield os.path.join(self.cache_dir, entry.name)
except (FileNotFoundError, OSError):
raise StopIteration
@@ -250,14 +261,17 @@ class FileCache(MutableMapping):
else:
return set(file_keys + list(self._buffer))
def _write_to_file(self, filename, bytesvalue):
def __write_to_file(self, filename, value):
"""Write bytesvalue to filename."""
fh, tmp = tempfile.mkstemp()
with os.fdopen(fh, self._flag) as f:
f.write(self._dumps(bytesvalue))
f.write(value)
rename(tmp, filename)
os.chmod(filename, self._mode)
def _write_to_file(self, filename, bytesvalue):
self.__write_to_file(filename, self._dumps(bytesvalue))
def _read_from_file(self, filename):
"""Read data from filename."""
try:
@@ -274,6 +288,7 @@ class FileCache(MutableMapping):
else:
filename = self._key_to_filename(ekey)
self._write_to_file(filename, value)
self.__write_to_file(filename + self.key_file_ext, ekey)
def __getitem__(self, key):
ekey = self._encode_key(key)
@@ -283,8 +298,9 @@ class FileCache(MutableMapping):
except KeyError:
pass
filename = self._key_to_filename(ekey)
if filename not in self._all_filenames():
if not os.path.isfile(filename):
raise KeyError(key)
return self._read_from_file(filename)
def __delitem__(self, key):
@@ -301,6 +317,11 @@ class FileCache(MutableMapping):
except (IOError, OSError):
pass
try:
os.remove(filename + self.key_file_ext)
except (IOError, OSError):
pass
def __iter__(self):
for key in self._all_keys():
yield self._decode_key(key)
@@ -310,4 +331,10 @@ class FileCache(MutableMapping):
def __contains__(self, key):
ekey = self._encode_key(key)
return ekey in self._all_keys()
if not self._sync:
try:
return ekey in self._buffer
except KeyError:
pass
filename = self._key_to_filename(ekey)
return os.path.isfile(filename)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,75 @@
# The MIT License
#
# Copyright 2014, 2015 Piotr Dabkowski
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the 'Software'),
# to deal in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
# the Software, and to permit persons to whom the Software is furnished to do so, subject
# to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all copies or
# substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
# LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
""" This module allows you to translate and execute Javascript in pure python.
Basically its implementation of ECMAScript 5.1 in pure python.
Use eval_js method to execute javascript code and get resulting python object (builtin if possible).
EXAMPLE:
>>> import js2py
>>> add = js2py.eval_js('function add(a, b) {return a + b}')
>>> add(1, 2) + 3
6
>>> add('1', 2, 3)
u'12'
>>> add.constructor
function Function() { [python code] }
Or use EvalJs to execute many javascript code fragments under same context - you would be able to get any
variable from the context!
>>> js = js2py.EvalJs()
>>> js.execute('var a = 10; function f(x) {return x*x};')
>>> js.f(9)
81
>>> js.a
10
Also you can use its console method to play with interactive javascript console.
Use parse_js to parse (syntax tree is just like in esprima.js) and translate_js to trasnlate JavaScript.
Finally, you can use pyimport statement from inside JS code to import and use python libraries.
>>> js2py.eval_js('pyimport urllib; urllib.urlopen("https://www.google.com")')
NOTE: This module is still not fully finished:
Date and JSON builtin objects are not implemented
Array prototype is not fully finished (will be soon)
Other than that everything should work fine.
"""
__author__ = 'Piotr Dabkowski'
__all__ = [
'EvalJs', 'translate_js', 'import_js', 'eval_js', 'parse_js',
'translate_file', 'run_file', 'disable_pyimport', 'eval_js6',
'translate_js6', 'PyJsException', 'get_file_contents',
'write_file_contents', 'require'
]
from .base import PyJsException
from .evaljs import *
from .translators import parse as parse_js
from .node_import import require
File diff suppressed because it is too large Load Diff
@@ -0,0 +1 @@
__author__ = 'Piotr Dabkowski'
@@ -0,0 +1,48 @@
from ..base import *
@Js
def Array():
if len(arguments) == 0 or len(arguments) > 1:
return arguments.to_list()
a = arguments[0]
if isinstance(a, PyJsNumber):
length = a.to_uint32()
if length != a.value:
raise MakeError('RangeError', 'Invalid array length')
temp = Js([])
temp.put('length', a)
return temp
return [a]
Array.create = Array
Array.own['length']['value'] = Js(1)
@Js
def isArray(arg):
return arg.Class == 'Array'
Array.define_own_property('isArray', {
'value': isArray,
'enumerable': False,
'writable': True,
'configurable': True
})
Array.define_own_property(
'prototype', {
'value': ArrayPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
ArrayPrototype.define_own_property('constructor', {
'value': Array,
'enumerable': False,
'writable': True,
'configurable': True
})
@@ -0,0 +1,41 @@
# this is based on jsarray.py
# todo check everything :)
from ..base import *
try:
import numpy
except:
pass
@Js
def ArrayBuffer():
a = arguments[0]
if isinstance(a, PyJsNumber):
length = a.to_uint32()
if length != a.value:
raise MakeError('RangeError', 'Invalid array length')
temp = Js(bytearray([0] * length))
return temp
return Js(bytearray([0]))
ArrayBuffer.create = ArrayBuffer
ArrayBuffer.own['length']['value'] = Js(None)
ArrayBuffer.define_own_property(
'prototype', {
'value': ArrayBufferPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
ArrayBufferPrototype.define_own_property(
'constructor', {
'value': ArrayBuffer,
'enumerable': False,
'writable': False,
'configurable': True
})
@@ -0,0 +1,16 @@
from ..base import *
BooleanPrototype.define_own_property('constructor', {
'value': Boolean,
'enumerable': False,
'writable': True,
'configurable': True
})
Boolean.define_own_property(
'prototype', {
'value': BooleanPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
@@ -0,0 +1,405 @@
from ..base import *
from .time_helpers import *
TZ_OFFSET = (time.altzone // 3600)
ABS_OFFSET = abs(TZ_OFFSET)
TZ_NAME = time.tzname[1]
ISO_FORMAT = '%s-%s-%sT%s:%s:%s.%sZ'
@Js
def Date(year, month, date, hours, minutes, seconds, ms):
return now().to_string()
Date.Class = 'Date'
def now():
return PyJsDate(int(time.time() * 1000), prototype=DatePrototype)
@Js
def UTC(year, month, date, hours, minutes, seconds, ms): # todo complete this
args = arguments
y = args[0].to_number()
m = args[1].to_number()
l = len(args)
dt = args[2].to_number() if l > 2 else Js(1)
h = args[3].to_number() if l > 3 else Js(0)
mi = args[4].to_number() if l > 4 else Js(0)
sec = args[5].to_number() if l > 5 else Js(0)
mili = args[6].to_number() if l > 6 else Js(0)
if not y.is_nan() and 0 <= y.value <= 99:
y = y + Js(1900)
t = TimeClip(MakeDate(MakeDay(y, m, dt), MakeTime(h, mi, sec, mili)))
return PyJsDate(t, prototype=DatePrototype)
@Js
def parse(string):
return PyJsDate(
TimeClip(parse_date(string.to_string().value)),
prototype=DatePrototype)
Date.define_own_property('now', {
'value': Js(now),
'enumerable': False,
'writable': True,
'configurable': True
})
Date.define_own_property('parse', {
'value': parse,
'enumerable': False,
'writable': True,
'configurable': True
})
Date.define_own_property('UTC', {
'value': UTC,
'enumerable': False,
'writable': True,
'configurable': True
})
class PyJsDate(PyJs):
Class = 'Date'
extensible = True
def __init__(self, value, prototype=None):
self.value = value
self.own = {}
self.prototype = prototype
# todo fix this problematic datetime part
def to_local_dt(self):
return datetime.datetime.utcfromtimestamp(
UTCToLocal(self.value) // 1000)
def to_utc_dt(self):
return datetime.datetime.utcfromtimestamp(self.value // 1000)
def local_strftime(self, pattern):
if self.value is NaN:
return 'Invalid Date'
try:
dt = self.to_local_dt()
except:
raise MakeError(
'TypeError',
'unsupported date range. Will fix in future versions')
try:
return dt.strftime(pattern)
except:
raise MakeError(
'TypeError',
'Could not generate date string from this date (limitations of python.datetime)'
)
def utc_strftime(self, pattern):
if self.value is NaN:
return 'Invalid Date'
try:
dt = self.to_utc_dt()
except:
raise MakeError(
'TypeError',
'unsupported date range. Will fix in future versions')
try:
return dt.strftime(pattern)
except:
raise MakeError(
'TypeError',
'Could not generate date string from this date (limitations of python.datetime)'
)
def parse_date(py_string): # todo support all date string formats
try:
try:
dt = datetime.datetime.strptime(py_string, "%Y-%m-%dT%H:%M:%S.%fZ")
except:
dt = datetime.datetime.strptime(py_string, "%Y-%m-%dT%H:%M:%SZ")
return MakeDate(
MakeDay(Js(dt.year), Js(dt.month - 1), Js(dt.day)),
MakeTime(
Js(dt.hour), Js(dt.minute), Js(dt.second),
Js(dt.microsecond // 1000)))
except:
raise MakeError(
'TypeError',
'Could not parse date %s - unsupported date format. Currently only supported format is RFC3339 utc. Sorry!'
% py_string)
def date_constructor(*args):
if len(args) >= 2:
return date_constructor2(*args)
elif len(args) == 1:
return date_constructor1(args[0])
else:
return date_constructor0()
def date_constructor0():
return now()
def date_constructor1(value):
v = value.to_primitive()
if v._type() == 'String':
v = parse_date(v.value)
else:
v = v.to_int()
return PyJsDate(TimeClip(v), prototype=DatePrototype)
def date_constructor2(*args):
y = args[0].to_number()
m = args[1].to_number()
l = len(args)
dt = args[2].to_number() if l > 2 else Js(1)
h = args[3].to_number() if l > 3 else Js(0)
mi = args[4].to_number() if l > 4 else Js(0)
sec = args[5].to_number() if l > 5 else Js(0)
mili = args[6].to_number() if l > 6 else Js(0)
if not y.is_nan() and 0 <= y.value <= 99:
y = y + Js(1900)
t = TimeClip(
LocalToUTC(MakeDate(MakeDay(y, m, dt), MakeTime(h, mi, sec, mili))))
return PyJsDate(t, prototype=DatePrototype)
Date.create = date_constructor
DatePrototype = PyJsDate(float('nan'), prototype=ObjectPrototype)
def check_date(obj):
if obj.Class != 'Date':
raise MakeError('TypeError', 'this is not a Date object')
class DateProto:
def toString():
check_date(this)
if this.value is NaN:
return 'Invalid Date'
offset = (UTCToLocal(this.value) - this.value) // msPerHour
return this.local_strftime(
'%a %b %d %Y %H:%M:%S GMT') + '%s00 (%s)' % (pad(
offset, 2, True), GetTimeZoneName(this.value))
def toDateString():
check_date(this)
return this.local_strftime('%d %B %Y')
def toTimeString():
check_date(this)
return this.local_strftime('%H:%M:%S')
def toLocaleString():
check_date(this)
return this.local_strftime('%d %B %Y %H:%M:%S')
def toLocaleDateString():
check_date(this)
return this.local_strftime('%d %B %Y')
def toLocaleTimeString():
check_date(this)
return this.local_strftime('%H:%M:%S')
def valueOf():
check_date(this)
return this.value
def getTime():
check_date(this)
return this.value
def getFullYear():
check_date(this)
if this.value is NaN:
return NaN
return YearFromTime(UTCToLocal(this.value))
def getUTCFullYear():
check_date(this)
if this.value is NaN:
return NaN
return YearFromTime(this.value)
def getMonth():
check_date(this)
if this.value is NaN:
return NaN
return MonthFromTime(UTCToLocal(this.value))
def getDate():
check_date(this)
if this.value is NaN:
return NaN
return DateFromTime(UTCToLocal(this.value))
def getUTCMonth():
check_date(this)
if this.value is NaN:
return NaN
return MonthFromTime(this.value)
def getUTCDate():
check_date(this)
if this.value is NaN:
return NaN
return DateFromTime(this.value)
def getDay():
check_date(this)
if this.value is NaN:
return NaN
return WeekDay(UTCToLocal(this.value))
def getUTCDay():
check_date(this)
if this.value is NaN:
return NaN
return WeekDay(this.value)
def getHours():
check_date(this)
if this.value is NaN:
return NaN
return HourFromTime(UTCToLocal(this.value))
def getUTCHours():
check_date(this)
if this.value is NaN:
return NaN
return HourFromTime(this.value)
def getMinutes():
check_date(this)
if this.value is NaN:
return NaN
return MinFromTime(UTCToLocal(this.value))
def getUTCMinutes():
check_date(this)
if this.value is NaN:
return NaN
return MinFromTime(this.value)
def getSeconds():
check_date(this)
if this.value is NaN:
return NaN
return SecFromTime(UTCToLocal(this.value))
def getUTCSeconds():
check_date(this)
if this.value is NaN:
return NaN
return SecFromTime(this.value)
def getMilliseconds():
check_date(this)
if this.value is NaN:
return NaN
return msFromTime(UTCToLocal(this.value))
def getUTCMilliseconds():
check_date(this)
if this.value is NaN:
return NaN
return msFromTime(this.value)
def getTimezoneOffset():
check_date(this)
if this.value is NaN:
return NaN
return (this.value - UTCToLocal(this.value)) // 60000
def setTime(time):
check_date(this)
this.value = TimeClip(time.to_number().to_int())
return this.value
def setMilliseconds(ms):
check_date(this)
t = UTCToLocal(this.value)
tim = MakeTime(
HourFromTime(t), MinFromTime(t), SecFromTime(t), ms.to_int())
u = TimeClip(LocalToUTC(MakeDate(Day(t), tim)))
this.value = u
return u
def setUTCMilliseconds(ms):
check_date(this)
t = this.value
tim = MakeTime(
HourFromTime(t), MinFromTime(t), SecFromTime(t), ms.to_int())
u = TimeClip(MakeDate(Day(t), tim))
this.value = u
return u
# todo Complete all setters!
def toUTCString():
check_date(this)
return this.utc_strftime('%d %B %Y %H:%M:%S')
def toISOString():
check_date(this)
t = this.value
year = YearFromTime(t)
month, day, hour, minute, second, milli = pad(
MonthFromTime(t) + 1), pad(DateFromTime(t)), pad(
HourFromTime(t)), pad(MinFromTime(t)), pad(
SecFromTime(t)), pad(msFromTime(t))
return ISO_FORMAT % (unicode(year) if 0 <= year <= 9999 else pad(
year, 6, True), month, day, hour, minute, second, milli)
def toJSON(key):
o = this.to_object()
tv = o.to_primitive('Number')
if tv.Class == 'Number' and not tv.is_finite():
return this.null
toISO = o.get('toISOString')
if not toISO.is_callable():
raise this.MakeError('TypeError', 'toISOString is not callable')
return toISO.call(o, ())
def pad(num, n=2, sign=False):
'''returns n digit string representation of the num'''
s = unicode(abs(num))
if len(s) < n:
s = '0' * (n - len(s)) + s
if not sign:
return s
if num >= 0:
return '+' + s
else:
return '-' + s
fill_prototype(DatePrototype, DateProto, default_attrs)
Date.define_own_property(
'prototype', {
'value': DatePrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
DatePrototype.define_own_property('constructor', {
'value': Date,
'enumerable': False,
'writable': True,
'configurable': True
})
@@ -0,0 +1,87 @@
# this is based on jsarray.py
from ..base import *
try:
import numpy
except:
pass
@Js
def Float32Array():
TypedArray = (PyJsInt8Array, PyJsUint8Array, PyJsUint8ClampedArray,
PyJsInt16Array, PyJsUint16Array, PyJsInt32Array,
PyJsUint32Array, PyJsFloat32Array, PyJsFloat64Array)
a = arguments[0]
if isinstance(a, PyJsNumber): # length
length = a.to_uint32()
if length != a.value:
raise MakeError('RangeError', 'Invalid array length')
temp = Js(numpy.full(length, 0, dtype=numpy.float32))
temp.put('length', a)
return temp
elif isinstance(a, PyJsString): # object (string)
temp = Js(numpy.array(list(a.value), dtype=numpy.float32))
temp.put('length', Js(len(list(a.value))))
return temp
elif isinstance(a, PyJsArray) or isinstance(a, TypedArray) or isinstance(
a, PyJsArrayBuffer): # object (Array, TypedArray)
array = a.to_list()
array = [(int(item.value) if item.value != None else 0)
for item in array]
temp = Js(numpy.array(array, dtype=numpy.float32))
temp.put('length', Js(len(array)))
return temp
elif isinstance(a, PyObjectWrapper): # object (ArrayBuffer, etc)
if len(a.obj) % 4 != 0:
raise MakeError(
'RangeError',
'Byte length of Float32Array should be a multiple of 4')
if len(arguments) > 1:
offset = int(arguments[1].value)
if offset % 4 != 0:
raise MakeError(
'RangeError',
'Start offset of Float32Array should be a multiple of 4')
else:
offset = 0
if len(arguments) > 2:
length = int(arguments[2].value)
else:
length = int((len(a.obj) - offset) / 4)
array = numpy.frombuffer(
a.obj, dtype=numpy.float32, count=length, offset=offset)
temp = Js(array)
temp.put('length', Js(length))
temp.buff = array
return temp
temp = Js(numpy.full(0, 0, dtype=numpy.float32))
temp.put('length', Js(0))
return temp
Float32Array.create = Float32Array
Float32Array.own['length']['value'] = Js(3)
Float32Array.define_own_property(
'prototype', {
'value': Float32ArrayPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
Float32ArrayPrototype.define_own_property(
'constructor', {
'value': Float32Array,
'enumerable': False,
'writable': True,
'configurable': True
})
Float32ArrayPrototype.define_own_property('BYTES_PER_ELEMENT', {
'value': Js(4),
'enumerable': False,
'writable': False,
'configurable': False
})
@@ -0,0 +1,87 @@
# this is based on jsarray.py
from ..base import *
try:
import numpy
except:
pass
@Js
def Float64Array():
TypedArray = (PyJsInt8Array, PyJsUint8Array, PyJsUint8ClampedArray,
PyJsInt16Array, PyJsUint16Array, PyJsInt32Array,
PyJsUint32Array, PyJsFloat32Array, PyJsFloat64Array)
a = arguments[0]
if isinstance(a, PyJsNumber): # length
length = a.to_uint32()
if length != a.value:
raise MakeError('RangeError', 'Invalid array length')
temp = Js(numpy.full(length, 0, dtype=numpy.float64))
temp.put('length', a)
return temp
elif isinstance(a, PyJsString): # object (string)
temp = Js(numpy.array(list(a.value), dtype=numpy.float64))
temp.put('length', Js(len(list(a.value))))
return temp
elif isinstance(a, PyJsArray) or isinstance(a, TypedArray) or isinstance(
a, PyJsArrayBuffer): # object (Array, TypedArray)
array = a.to_list()
array = [(int(item.value) if item.value != None else 0)
for item in array]
temp = Js(numpy.array(array, dtype=numpy.float64))
temp.put('length', Js(len(array)))
return temp
elif isinstance(a, PyObjectWrapper): # object (ArrayBuffer, etc)
if len(a.obj) % 8 != 0:
raise MakeError(
'RangeError',
'Byte length of Float64Array should be a multiple of 8')
if len(arguments) > 1:
offset = int(arguments[1].value)
if offset % 8 != 0:
raise MakeError(
'RangeError',
'Start offset of Float64Array should be a multiple of 8')
else:
offset = 0
if len(arguments) > 2:
length = int(arguments[2].value)
else:
length = int((len(a.obj) - offset) / 8)
array = numpy.frombuffer(
a.obj, dtype=numpy.float64, count=length, offset=offset)
temp = Js(array)
temp.put('length', Js(length))
temp.buff = array
return temp
temp = Js(numpy.full(0, 0, dtype=numpy.float64))
temp.put('length', Js(0))
return temp
Float64Array.create = Float64Array
Float64Array.own['length']['value'] = Js(3)
Float64Array.define_own_property(
'prototype', {
'value': Float64ArrayPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
Float64ArrayPrototype.define_own_property(
'constructor', {
'value': Float64Array,
'enumerable': False,
'writable': True,
'configurable': True
})
Float64ArrayPrototype.define_own_property('BYTES_PER_ELEMENT', {
'value': Js(8),
'enumerable': False,
'writable': False,
'configurable': False
})
@@ -0,0 +1,52 @@
from ..base import *
try:
from ..translators.translator import translate_js
except:
pass
@Js
def Function():
# convert arguments to python list of strings
a = [e.to_string().value for e in arguments.to_list()]
body = ';'
args = ()
if len(a):
body = '%s;' % a[-1]
args = a[:-1]
# translate this function to js inline function
js_func = '(function (%s) {%s})' % (','.join(args), body)
# now translate js inline to python function
py_func = translate_js(js_func, '')
# add set func scope to global scope
# a but messy solution but works :)
globals()['var'] = PyJs.GlobalObject
# define py function and return it
temp = executor(py_func, globals())
temp.source = '{%s}' % body
temp.func_name = 'anonymous'
return temp
def executor(f, glob):
exec (f, globals())
return globals()['PyJs_anonymous_0_']
#new statement simply calls Function
Function.create = Function
#set constructor property inside FunctionPrototype
fill_in_props(FunctionPrototype, {'constructor': Function}, default_attrs)
#attach prototype to Function constructor
Function.define_own_property(
'prototype', {
'value': FunctionPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
#Fix Function length (its 0 and should be 1)
Function.own['length']['value'] = Js(1)
@@ -0,0 +1,87 @@
# this is based on jsarray.py
from ..base import *
try:
import numpy
except:
pass
@Js
def Int16Array():
TypedArray = (PyJsInt8Array, PyJsUint8Array, PyJsUint8ClampedArray,
PyJsInt16Array, PyJsUint16Array, PyJsInt32Array,
PyJsUint32Array, PyJsFloat32Array, PyJsFloat64Array)
a = arguments[0]
if isinstance(a, PyJsNumber): # length
length = a.to_uint32()
if length != a.value:
raise MakeError('RangeError', 'Invalid array length')
temp = Js(numpy.full(length, 0, dtype=numpy.int16))
temp.put('length', a)
return temp
elif isinstance(a, PyJsString): # object (string)
temp = Js(numpy.array(list(a.value), dtype=numpy.int16))
temp.put('length', Js(len(list(a.value))))
return temp
elif isinstance(a, PyJsArray) or isinstance(a, TypedArray) or isinstance(
a, PyJsArrayBuffer): # object (Array, TypedArray)
array = a.to_list()
array = [(int(item.value) if item.value != None else 0)
for item in array]
temp = Js(numpy.array(array, dtype=numpy.int16))
temp.put('length', Js(len(array)))
return temp
elif isinstance(a, PyObjectWrapper): # object (ArrayBuffer, etc)
if len(a.obj) % 2 != 0:
raise MakeError(
'RangeError',
'Byte length of Int16Array should be a multiple of 2')
if len(arguments) > 1:
offset = int(arguments[1].value)
if offset % 2 != 0:
raise MakeError(
'RangeError',
'Start offset of Int16Array should be a multiple of 2')
else:
offset = 0
if len(arguments) > 2:
length = int(arguments[2].value)
else:
length = int((len(a.obj) - offset) / 2)
array = numpy.frombuffer(
a.obj, dtype=numpy.int16, count=length, offset=offset)
temp = Js(array)
temp.put('length', Js(length))
temp.buff = array
return temp
temp = Js(numpy.full(0, 0, dtype=numpy.int16))
temp.put('length', Js(0))
return temp
Int16Array.create = Int16Array
Int16Array.own['length']['value'] = Js(3)
Int16Array.define_own_property(
'prototype', {
'value': Int16ArrayPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
Int16ArrayPrototype.define_own_property(
'constructor', {
'value': Int16Array,
'enumerable': False,
'writable': True,
'configurable': True
})
Int16ArrayPrototype.define_own_property('BYTES_PER_ELEMENT', {
'value': Js(2),
'enumerable': False,
'writable': False,
'configurable': False
})
@@ -0,0 +1,87 @@
# this is based on jsarray.py
from ..base import *
try:
import numpy
except:
pass
@Js
def Int32Array():
TypedArray = (PyJsInt8Array, PyJsUint8Array, PyJsUint8ClampedArray,
PyJsInt16Array, PyJsUint16Array, PyJsInt32Array,
PyJsUint32Array, PyJsFloat32Array, PyJsFloat64Array)
a = arguments[0]
if isinstance(a, PyJsNumber): # length
length = a.to_uint32()
if length != a.value:
raise MakeError('RangeError', 'Invalid array length')
temp = Js(numpy.full(length, 0, dtype=numpy.int32))
temp.put('length', a)
return temp
elif isinstance(a, PyJsString): # object (string)
temp = Js(numpy.array(list(a.value), dtype=numpy.int32))
temp.put('length', Js(len(list(a.value))))
return temp
elif isinstance(a, PyJsArray) or isinstance(a, TypedArray) or isinstance(
a, PyJsArrayBuffer): # object (Array, TypedArray)
array = a.to_list()
array = [(int(item.value) if item.value != None else 0)
for item in array]
temp = Js(numpy.array(array, dtype=numpy.int32))
temp.put('length', Js(len(array)))
return temp
elif isinstance(a, PyObjectWrapper): # object (ArrayBuffer, etc)
if len(a.obj) % 4 != 0:
raise MakeError(
'RangeError',
'Byte length of Int32Array should be a multiple of 4')
if len(arguments) > 1:
offset = int(arguments[1].value)
if offset % 4 != 0:
raise MakeError(
'RangeError',
'Start offset of Int32Array should be a multiple of 4')
else:
offset = 0
if len(arguments) > 2:
length = int(arguments[2].value)
else:
length = int((len(a.obj) - offset) / 4)
array = numpy.frombuffer(
a.obj, dtype=numpy.int32, count=length, offset=offset)
temp = Js(array)
temp.put('length', Js(length))
temp.buff = array
return temp
temp = Js(numpy.full(0, 0, dtype=numpy.int32))
temp.put('length', Js(0))
return temp
Int32Array.create = Int32Array
Int32Array.own['length']['value'] = Js(3)
Int32Array.define_own_property(
'prototype', {
'value': Int32ArrayPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
Int32ArrayPrototype.define_own_property(
'constructor', {
'value': Int32Array,
'enumerable': False,
'writable': True,
'configurable': True
})
Int32ArrayPrototype.define_own_property('BYTES_PER_ELEMENT', {
'value': Js(4),
'enumerable': False,
'writable': False,
'configurable': False
})
@@ -0,0 +1,79 @@
# this is based on jsarray.py
from ..base import *
try:
import numpy
except:
pass
@Js
def Int8Array():
TypedArray = (PyJsInt8Array, PyJsUint8Array, PyJsUint8ClampedArray,
PyJsInt16Array, PyJsUint16Array, PyJsInt32Array,
PyJsUint32Array, PyJsFloat32Array, PyJsFloat64Array)
a = arguments[0]
if isinstance(a, PyJsNumber): # length
length = a.to_uint32()
if length != a.value:
raise MakeError('RangeError', 'Invalid array length')
temp = Js(numpy.full(length, 0, dtype=numpy.int8))
temp.put('length', a)
return temp
elif isinstance(a, PyJsString): # object (string)
temp = Js(numpy.array(list(a.value), dtype=numpy.int8))
temp.put('length', Js(len(list(a.value))))
return temp
elif isinstance(a, PyJsArray) or isinstance(a, TypedArray) or isinstance(
a, PyJsArrayBuffer): # object (Array, TypedArray)
array = a.to_list()
array = [(int(item.value) if item.value != None else 0)
for item in array]
temp = Js(numpy.array(array, dtype=numpy.int8))
temp.put('length', Js(len(array)))
return temp
elif isinstance(a, PyObjectWrapper): # object (ArrayBuffer, etc)
if len(arguments) > 1:
offset = int(arguments[1].value)
else:
offset = 0
if len(arguments) > 2:
length = int(arguments[2].value)
else:
length = int(len(a.obj) - offset)
array = numpy.frombuffer(
a.obj, dtype=numpy.int8, count=length, offset=offset)
temp = Js(array)
temp.put('length', Js(length))
temp.buff = array
return temp
temp = Js(numpy.full(0, 0, dtype=numpy.int8))
temp.put('length', Js(0))
return temp
Int8Array.create = Int8Array
Int8Array.own['length']['value'] = Js(3)
Int8Array.define_own_property(
'prototype', {
'value': Int8ArrayPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
Int8ArrayPrototype.define_own_property(
'constructor', {
'value': Int8Array,
'enumerable': False,
'writable': True,
'configurable': True
})
Int8ArrayPrototype.define_own_property('BYTES_PER_ELEMENT', {
'value': Js(1),
'enumerable': False,
'writable': False,
'configurable': False
})
@@ -0,0 +1,157 @@
from ..base import *
import math
import random
Math = PyJsObject(prototype=ObjectPrototype)
Math.Class = 'Math'
CONSTANTS = {
'E': 2.7182818284590452354,
'LN10': 2.302585092994046,
'LN2': 0.6931471805599453,
'LOG2E': 1.4426950408889634,
'LOG10E': 0.4342944819032518,
'PI': 3.1415926535897932,
'SQRT1_2': 0.7071067811865476,
'SQRT2': 1.4142135623730951
}
for constant, value in CONSTANTS.items():
Math.define_own_property(
constant, {
'value': Js(value),
'writable': False,
'enumerable': False,
'configurable': False
})
class MathFunctions:
def abs(x):
a = x.to_number().value
if a != a: # it must be a nan
return NaN
return abs(a)
def acos(x):
a = x.to_number().value
if a != a: # it must be a nan
return NaN
try:
return math.acos(a)
except:
return NaN
def asin(x):
a = x.to_number().value
if a != a: # it must be a nan
return NaN
try:
return math.asin(a)
except:
return NaN
def atan(x):
a = x.to_number().value
if a != a: # it must be a nan
return NaN
return math.atan(a)
def atan2(y, x):
a = x.to_number().value
b = y.to_number().value
if a != a or b != b: # it must be a nan
return NaN
return math.atan2(b, a)
def ceil(x):
a = x.to_number().value
if a != a: # it must be a nan
return NaN
return math.ceil(a)
def floor(x):
a = x.to_number().value
if a != a: # it must be a nan
return NaN
return math.floor(a)
def round(x):
a = x.to_number().value
if a != a: # it must be a nan
return NaN
return round(a)
def sin(x):
a = x.to_number().value
if a != a: # it must be a nan
return NaN
return math.sin(a)
def cos(x):
a = x.to_number().value
if a != a: # it must be a nan
return NaN
return math.cos(a)
def tan(x):
a = x.to_number().value
if a != a: # it must be a nan
return NaN
return math.tan(a)
def log(x):
a = x.to_number().value
if a != a: # it must be a nan
return NaN
try:
return math.log(a)
except:
return NaN
def exp(x):
a = x.to_number().value
if a != a: # it must be a nan
return NaN
return math.exp(a)
def pow(x, y):
a = x.to_number().value
b = y.to_number().value
if a != a or b != b: # it must be a nan
return NaN
try:
return a**b
except:
return NaN
def sqrt(x):
a = x.to_number().value
if a != a: # it must be a nan
return NaN
try:
return a**0.5
except:
return NaN
def min():
if not len(arguments):
return Infinity
lis = tuple(e.to_number().value for e in arguments.to_list())
if any(e != e for e in lis): # we dont want NaNs
return NaN
return min(*lis)
def max():
if not len(arguments):
return -Infinity
lis = tuple(e.to_number().value for e in arguments.to_list())
if any(e != e for e in lis): # we dont want NaNs
return NaN
return max(*lis)
def random():
return random.random()
fill_prototype(Math, MathFunctions, default_attrs)
@@ -0,0 +1,23 @@
from ..base import *
CONSTS = {
'prototype': NumberPrototype,
'MAX_VALUE': 1.7976931348623157e308,
'MIN_VALUE': 5.0e-324,
'NaN': NaN,
'NEGATIVE_INFINITY': float('-inf'),
'POSITIVE_INFINITY': float('inf')
}
fill_in_props(Number, CONSTS, {
'enumerable': False,
'writable': False,
'configurable': False
})
NumberPrototype.define_own_property('constructor', {
'value': Number,
'enumerable': False,
'writable': True,
'configurable': True
})
@@ -0,0 +1,198 @@
from ..base import *
import six
#todo Double check everything is OK
@Js
def Object():
val = arguments.get('0')
if val.is_null() or val.is_undefined():
return PyJsObject(prototype=ObjectPrototype)
return val.to_object()
@Js
def object_constructor():
if len(arguments):
val = arguments.get('0')
if val.TYPE == 'Object':
#Implementation dependent, but my will simply return :)
return val
elif val.TYPE in ('Number', 'String', 'Boolean'):
return val.to_object()
return PyJsObject(prototype=ObjectPrototype)
Object.create = object_constructor
Object.own['length']['value'] = Js(1)
class ObjectMethods:
def getPrototypeOf(obj):
if not obj.is_object():
raise MakeError('TypeError',
'Object.getPrototypeOf called on non-object')
return null if obj.prototype is None else obj.prototype
def getOwnPropertyDescriptor(obj, prop):
if not obj.is_object():
raise MakeError(
'TypeError',
'Object.getOwnPropertyDescriptor called on non-object')
return obj.own.get(
prop.to_string().
value) # will return undefined if we dont have this prop
def getOwnPropertyNames(obj):
if not obj.is_object():
raise MakeError(
'TypeError',
'Object.getOwnPropertyDescriptor called on non-object')
return obj.own.keys()
def create(obj):
if not (obj.is_object() or obj.is_null()):
raise MakeError('TypeError',
'Object prototype may only be an Object or null')
temp = PyJsObject(prototype=(None if obj.is_null() else obj))
if len(arguments) > 1 and not arguments[1].is_undefined():
if six.PY2:
ObjectMethods.defineProperties.__func__(temp, arguments[1])
else:
ObjectMethods.defineProperties(temp, arguments[1])
return temp
def defineProperty(obj, prop, attrs):
if not obj.is_object():
raise MakeError('TypeError',
'Object.defineProperty called on non-object')
name = prop.to_string().value
if not obj.define_own_property(name, ToPropertyDescriptor(attrs)):
raise MakeError('TypeError', 'Cannot redefine property: %s' % name)
return obj
def defineProperties(obj, properties):
if not obj.is_object():
raise MakeError('TypeError',
'Object.defineProperties called on non-object')
props = properties.to_object()
for name in props:
desc = ToPropertyDescriptor(props.get(name.value))
if not obj.define_own_property(name.value, desc):
raise MakeError(
'TypeError',
'Failed to define own property: %s' % name.value)
return obj
def seal(obj):
if not obj.is_object():
raise MakeError('TypeError', 'Object.seal called on non-object')
for desc in obj.own.values():
desc['configurable'] = False
obj.extensible = False
return obj
def freeze(obj):
if not obj.is_object():
raise MakeError('TypeError', 'Object.freeze called on non-object')
for desc in obj.own.values():
desc['configurable'] = False
if is_data_descriptor(desc):
desc['writable'] = False
obj.extensible = False
return obj
def preventExtensions(obj):
if not obj.is_object():
raise MakeError('TypeError',
'Object.preventExtensions on non-object')
obj.extensible = False
return obj
def isSealed(obj):
if not obj.is_object():
raise MakeError('TypeError',
'Object.isSealed called on non-object')
if obj.extensible:
return False
for desc in obj.own.values():
if desc['configurable']:
return False
return True
def isFrozen(obj):
if not obj.is_object():
raise MakeError('TypeError',
'Object.isFrozen called on non-object')
if obj.extensible:
return False
for desc in obj.own.values():
if desc['configurable']:
return False
if is_data_descriptor(desc) and desc['writable']:
return False
return True
def isExtensible(obj):
if not obj.is_object():
raise MakeError('TypeError',
'Object.isExtensible called on non-object')
return obj.extensible
def keys(obj):
if not obj.is_object():
raise MakeError('TypeError', 'Object.keys called on non-object')
return [e for e, d in six.iteritems(obj.own) if d.get('enumerable')]
# add methods attached to Object constructor
fill_prototype(Object, ObjectMethods, default_attrs)
# add constructor to prototype
fill_in_props(ObjectPrototype, {'constructor': Object}, default_attrs)
# add prototype property to the constructor.
Object.define_own_property(
'prototype', {
'value': ObjectPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
# some utility functions:
def ToPropertyDescriptor(obj): # page 38 (50 absolute)
if obj.TYPE != 'Object':
raise MakeError('TypeError',
'Can\'t convert non-object to property descriptor')
desc = {}
if obj.has_property('enumerable'):
desc['enumerable'] = obj.get('enumerable').to_boolean().value
if obj.has_property('configurable'):
desc['configurable'] = obj.get('configurable').to_boolean().value
if obj.has_property('value'):
desc['value'] = obj.get('value')
if obj.has_property('writable'):
desc['writable'] = obj.get('writable').to_boolean().value
if obj.has_property('get'):
cand = obj.get('get')
if not (cand.is_undefined() or cand.is_callable()):
raise MakeError(
'TypeError',
'Invalid getter (it has to be a function or undefined)')
desc['get'] = cand
if obj.has_property('set'):
cand = obj.get('set')
if not (cand.is_undefined() or cand.is_callable()):
raise MakeError(
'TypeError',
'Invalid setter (it has to be a function or undefined)')
desc['set'] = cand
if ('get' in desc or 'set' in desc) and ('value' in desc
or 'writable' in desc):
raise MakeError(
'TypeError',
'Invalid property. A property cannot both have accessors and be writable or have a value.'
)
return desc
@@ -0,0 +1,16 @@
from ..base import *
RegExpPrototype.define_own_property('constructor', {
'value': RegExp,
'enumerable': False,
'writable': True,
'configurable': True
})
RegExp.define_own_property(
'prototype', {
'value': RegExpPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
@@ -0,0 +1,40 @@
from ..base import *
# python 3 support
import six
if six.PY3:
unichr = chr
@Js
def fromCharCode():
args = arguments.to_list()
res = u''
for e in args:
res += unichr(e.to_uint16())
return this.Js(res)
fromCharCode.own['length']['value'] = Js(1)
String.define_own_property(
'fromCharCode', {
'value': fromCharCode,
'enumerable': False,
'writable': True,
'configurable': True
})
String.define_own_property(
'prototype', {
'value': StringPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
StringPrototype.define_own_property('constructor', {
'value': String,
'enumerable': False,
'writable': True,
'configurable': True
})
@@ -0,0 +1,87 @@
# this is based on jsarray.py
from ..base import *
try:
import numpy
except:
pass
@Js
def Uint16Array():
TypedArray = (PyJsInt8Array, PyJsUint8Array, PyJsUint8ClampedArray,
PyJsInt16Array, PyJsUint16Array, PyJsInt32Array,
PyJsUint32Array, PyJsFloat32Array, PyJsFloat64Array)
a = arguments[0]
if isinstance(a, PyJsNumber): # length
length = a.to_uint32()
if length != a.value:
raise MakeError('RangeError', 'Invalid array length')
temp = Js(numpy.full(length, 0, dtype=numpy.uint16))
temp.put('length', a)
return temp
elif isinstance(a, PyJsString): # object (string)
temp = Js(numpy.array(list(a.value), dtype=numpy.uint16))
temp.put('length', Js(len(list(a.value))))
return temp
elif isinstance(a, PyJsArray) or isinstance(a, TypedArray) or isinstance(
a, PyJsArrayBuffer): # object (Array, TypedArray)
array = a.to_list()
array = [(int(item.value) if item.value != None else 0)
for item in array]
temp = Js(numpy.array(array, dtype=numpy.uint16))
temp.put('length', Js(len(array)))
return temp
elif isinstance(a, PyObjectWrapper): # object (ArrayBuffer, etc)
if len(a.obj) % 2 != 0:
raise MakeError(
'RangeError',
'Byte length of Uint16Array should be a multiple of 2')
if len(arguments) > 1:
offset = int(arguments[1].value)
if offset % 2 != 0:
raise MakeError(
'RangeError',
'Start offset of Uint16Array should be a multiple of 2')
else:
offset = 0
if len(arguments) > 2:
length = int(arguments[2].value)
else:
length = int((len(a.obj) - offset) / 2)
array = numpy.frombuffer(
a.obj, dtype=numpy.uint16, count=length, offset=offset)
temp = Js(array)
temp.put('length', Js(length))
temp.buff = array
return temp
temp = Js(numpy.full(0, 0, dtype=numpy.uint16))
temp.put('length', Js(0))
return temp
Uint16Array.create = Uint16Array
Uint16Array.own['length']['value'] = Js(3)
Uint16Array.define_own_property(
'prototype', {
'value': Uint16ArrayPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
Uint16ArrayPrototype.define_own_property(
'constructor', {
'value': Uint16Array,
'enumerable': False,
'writable': True,
'configurable': True
})
Uint16ArrayPrototype.define_own_property('BYTES_PER_ELEMENT', {
'value': Js(2),
'enumerable': False,
'writable': False,
'configurable': False
})
@@ -0,0 +1,95 @@
# this is based on jsarray.py
from ..base import *
try:
import numpy
except:
pass
@Js
def Uint32Array():
TypedArray = (PyJsInt8Array, PyJsUint8Array, PyJsUint8ClampedArray,
PyJsInt16Array, PyJsUint16Array, PyJsInt32Array,
PyJsUint32Array, PyJsFloat32Array, PyJsFloat64Array)
a = arguments[0]
if isinstance(a, PyJsNumber): # length
length = a.to_uint32()
if length != a.value:
raise MakeError('RangeError', 'Invalid array length')
temp = Js(numpy.full(length, 0, dtype=numpy.uint32))
temp.put('length', a)
return temp
elif isinstance(a, PyJsString): # object (string)
temp = Js(numpy.array(list(a.value), dtype=numpy.uint32))
temp.put('length', Js(len(list(a.value))))
return temp
elif isinstance(a, PyJsArray) or isinstance(a, TypedArray) or isinstance(
a, PyJsArrayBuffer): # object (Array, TypedArray)
array = a.to_list()
array = [(int(item.value) if item.value != None else 0)
for item in array]
if len(arguments) > 1:
offset = int(arguments[1].value)
else:
offset = 0
if len(arguments) > 2:
length = int(arguments[2].value)
else:
length = len(array) - offset
temp = Js(
numpy.array(array[offset:offset + length], dtype=numpy.uint32))
temp.put('length', Js(length))
return temp
elif isinstance(a, PyObjectWrapper): # object (ArrayBuffer, etc)
if len(a.obj) % 4 != 0:
raise MakeError(
'RangeError',
'Byte length of Uint32Array should be a multiple of 4')
if len(arguments) > 1:
offset = int(arguments[1].value)
if offset % 4 != 0:
raise MakeError(
'RangeError',
'Start offset of Uint32Array should be a multiple of 4')
else:
offset = 0
if len(arguments) > 2:
length = int(arguments[2].value)
else:
length = int((len(a.obj) - offset) / 4)
temp = Js(
numpy.frombuffer(
a.obj, dtype=numpy.uint32, count=length, offset=offset))
temp.put('length', Js(length))
return temp
temp = Js(numpy.full(0, 0, dtype=numpy.uint32))
temp.put('length', Js(0))
return temp
Uint32Array.create = Uint32Array
Uint32Array.own['length']['value'] = Js(3)
Uint32Array.define_own_property(
'prototype', {
'value': Uint32ArrayPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
Uint32ArrayPrototype.define_own_property(
'constructor', {
'value': Uint32Array,
'enumerable': False,
'writable': True,
'configurable': True
})
Uint32ArrayPrototype.define_own_property('BYTES_PER_ELEMENT', {
'value': Js(4),
'enumerable': False,
'writable': False,
'configurable': False
})
@@ -0,0 +1,79 @@
# this is based on jsarray.py
from ..base import *
try:
import numpy
except:
pass
@Js
def Uint8Array():
TypedArray = (PyJsInt8Array, PyJsUint8Array, PyJsUint8ClampedArray,
PyJsInt16Array, PyJsUint16Array, PyJsInt32Array,
PyJsUint32Array, PyJsFloat32Array, PyJsFloat64Array)
a = arguments[0]
if isinstance(a, PyJsNumber): # length
length = a.to_uint32()
if length != a.value:
raise MakeError('RangeError', 'Invalid array length')
temp = Js(numpy.full(length, 0, dtype=numpy.uint8))
temp.put('length', a)
return temp
elif isinstance(a, PyJsString): # object (string)
temp = Js(numpy.array(list(a.value), dtype=numpy.uint8))
temp.put('length', Js(len(list(a.value))))
return temp
elif isinstance(a, PyJsArray) or isinstance(a, TypedArray) or isinstance(
a, PyJsArrayBuffer): # object (Array, TypedArray)
array = a.to_list()
array = [(int(item.value) if item.value != None else 0)
for item in array]
temp = Js(numpy.array(array, dtype=numpy.uint8))
temp.put('length', Js(len(array)))
return temp
elif isinstance(a, PyObjectWrapper): # object (ArrayBuffer, etc)
if len(arguments) > 1:
offset = int(arguments[1].value)
else:
offset = 0
if len(arguments) > 2:
length = int(arguments[2].value)
else:
length = int(len(a.obj) - offset)
array = numpy.frombuffer(
a.obj, dtype=numpy.uint8, count=length, offset=offset)
temp = Js(array)
temp.put('length', Js(length))
temp.buff = array
return temp
temp = Js(numpy.full(0, 0, dtype=numpy.uint8))
temp.put('length', Js(0))
return temp
Uint8Array.create = Uint8Array
Uint8Array.own['length']['value'] = Js(3)
Uint8Array.define_own_property(
'prototype', {
'value': Uint8ArrayPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
Uint8ArrayPrototype.define_own_property(
'constructor', {
'value': Uint8Array,
'enumerable': False,
'writable': True,
'configurable': True
})
Uint8ArrayPrototype.define_own_property('BYTES_PER_ELEMENT', {
'value': Js(1),
'enumerable': False,
'writable': False,
'configurable': False
})
@@ -0,0 +1,79 @@
# this is based on jsarray.py
from ..base import *
try:
import numpy
except:
pass
@Js
def Uint8ClampedArray():
TypedArray = (PyJsInt8Array, PyJsUint8Array, PyJsUint8ClampedArray,
PyJsInt16Array, PyJsUint16Array, PyJsInt32Array,
PyJsUint32Array, PyJsFloat32Array, PyJsFloat64Array)
a = arguments[0]
if isinstance(a, PyJsNumber): # length
length = a.to_uint32()
if length != a.value:
raise MakeError('RangeError', 'Invalid array length')
temp = Js(numpy.full(length, 0, dtype=numpy.uint8), Clamped=True)
temp.put('length', a)
return temp
elif isinstance(a, PyJsString): # object (string)
temp = Js(numpy.array(list(a.value), dtype=numpy.uint8), Clamped=True)
temp.put('length', Js(len(list(a.value))))
return temp
elif isinstance(a, PyJsArray) or isinstance(a, TypedArray) or isinstance(
a, PyJsArrayBuffer): # object (Array, TypedArray)
array = a.to_list()
array = [(int(item.value) if item.value != None else 0)
for item in array]
temp = Js(numpy.array(array, dtype=numpy.uint8), Clamped=True)
temp.put('length', Js(len(array)))
return temp
elif isinstance(a, PyObjectWrapper): # object (ArrayBuffer, etc)
if len(arguments) > 1:
offset = int(arguments[1].value)
else:
offset = 0
if len(arguments) > 2:
length = int(arguments[2].value)
else:
length = int(len(a.obj) - offset)
array = numpy.frombuffer(
a.obj, dtype=numpy.uint8, count=length, offset=offset)
temp = Js(array, Clamped=True)
temp.put('length', Js(length))
temp.buff = array
return temp
temp = Js(numpy.full(0, 0, dtype=numpy.uint8), Clamped=True)
temp.put('length', Js(0))
return temp
Uint8ClampedArray.create = Uint8ClampedArray
Uint8ClampedArray.own['length']['value'] = Js(3)
Uint8ClampedArray.define_own_property(
'prototype', {
'value': Uint8ClampedArrayPrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
Uint8ClampedArrayPrototype.define_own_property(
'constructor', {
'value': Uint8ClampedArray,
'enumerable': False,
'writable': True,
'configurable': True
})
Uint8ClampedArrayPrototype.define_own_property('BYTES_PER_ELEMENT', {
'value': Js(1),
'enumerable': False,
'writable': False,
'configurable': False
})
@@ -0,0 +1,207 @@
# NOTE: t must be INT!!!
import time
import datetime
import warnings
try:
from tzlocal import get_localzone
LOCAL_ZONE = get_localzone()
except: # except all problems...
warnings.warn(
'Please install or fix tzlocal library (pip install tzlocal) in order to make Date object work better. Otherwise I will assume DST is in effect all the time'
)
class LOCAL_ZONE:
@staticmethod
def dst(*args):
return 1
from js2py.base import MakeError
CUM = (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365)
msPerDay = 86400000
msPerYear = int(86400000 * 365.242)
msPerSecond = 1000
msPerMinute = 60000
msPerHour = 3600000
HoursPerDay = 24
MinutesPerHour = 60
SecondsPerMinute = 60
NaN = float('nan')
LocalTZA = -time.timezone * msPerSecond
def DaylightSavingTA(t):
if t is NaN:
return t
try:
return int(
LOCAL_ZONE.dst(datetime.datetime.utcfromtimestamp(
t // 1000)).seconds) * 1000
except:
warnings.warn(
'Invalid datetime date, assumed DST time, may be inaccurate...',
Warning)
return 1
#raise MakeError('TypeError', 'date not supported by python.datetime. I will solve it in future versions')
def GetTimeZoneName(t):
return time.tzname[DaylightSavingTA(t) > 0]
def LocalToUTC(t):
return t - LocalTZA - DaylightSavingTA(t - LocalTZA)
def UTCToLocal(t):
return t + LocalTZA + DaylightSavingTA(t)
def Day(t):
return t // 86400000
def TimeWithinDay(t):
return t % 86400000
def DaysInYear(y):
if y % 4:
return 365
elif y % 100:
return 366
elif y % 400:
return 365
else:
return 366
def DayFromYear(y):
return 365 * (y - 1970) + (y - 1969) // 4 - (y - 1901) // 100 + (
y - 1601) // 400
def TimeFromYear(y):
return 86400000 * DayFromYear(y)
def YearFromTime(t):
guess = 1970 - t // 31556908800 # msPerYear
gt = TimeFromYear(guess)
if gt <= t:
while gt <= t:
guess += 1
gt = TimeFromYear(guess)
return guess - 1
else:
while gt > t:
guess -= 1
gt = TimeFromYear(guess)
return guess
def DayWithinYear(t):
return Day(t) - DayFromYear(YearFromTime(t))
def InLeapYear(t):
y = YearFromTime(t)
if y % 4:
return 0
elif y % 100:
return 1
elif y % 400:
return 0
else:
return 1
def MonthFromTime(t):
day = DayWithinYear(t)
leap = InLeapYear(t)
if day < 31:
return 0
day -= leap
if day < 59:
return 1
elif day < 90:
return 2
elif day < 120:
return 3
elif day < 151:
return 4
elif day < 181:
return 5
elif day < 212:
return 6
elif day < 243:
return 7
elif day < 273:
return 8
elif day < 304:
return 9
elif day < 334:
return 10
else:
return 11
def DateFromTime(t):
mon = MonthFromTime(t)
day = DayWithinYear(t)
return day - CUM[mon] - (1 if InLeapYear(t) and mon >= 2 else 0) + 1
def WeekDay(t):
# 0 == sunday
return (Day(t) + 4) % 7
def msFromTime(t):
return t % 1000
def SecFromTime(t):
return (t // 1000) % 60
def MinFromTime(t):
return (t // 60000) % 60
def HourFromTime(t):
return (t // 3600000) % 24
def MakeTime(hour, Min, sec, ms):
# takes PyJs objects and returns t
if not (hour.is_finite() and Min.is_finite() and sec.is_finite()
and ms.is_finite()):
return NaN
h, m, s, milli = hour.to_int(), Min.to_int(), sec.to_int(), ms.to_int()
return h * 3600000 + m * 60000 + s * 1000 + milli
def MakeDay(year, month, date):
# takes PyJs objects and returns t
if not (year.is_finite() and month.is_finite() and date.is_finite()):
return NaN
y, m, dt = year.to_int(), month.to_int(), date.to_int()
y += m // 12
mn = m % 12
d = DayFromYear(y) + CUM[mn] + dt - 1 + (1 if DaysInYear(y) == 366
and mn >= 2 else 0)
return d # ms per day
def MakeDate(day, time):
return 86400000 * day + time
def TimeClip(t):
if t != t or abs(t) == float('inf'):
return NaN
if abs(t) > 8.64 * 10**15:
return NaN
return int(t)
@@ -0,0 +1,41 @@
INITIALISED = False
babel = None
babelPresetEs2015 = None
def js6_to_js5(code):
global INITIALISED, babel, babelPresetEs2015
if not INITIALISED:
import signal, warnings, time
warnings.warn(
'\nImporting babel.py for the first time - this can take some time. \nPlease note that currently Javascript 6 in Js2Py is unstable and slow. Use only for tiny scripts!'
)
from .babel import babel as _babel
babel = _babel.Object.babel
babelPresetEs2015 = _babel.Object.babelPresetEs2015
# very weird hack. Somehow this helps babel to initialise properly!
try:
babel.transform('warmup', {'presets': {}})
signal.alarm(2)
def kill_it(a, b):
raise KeyboardInterrupt('Better work next time!')
signal.signal(signal.SIGALRM, kill_it)
babel.transform('stuckInALoop', {
'presets': babelPresetEs2015
}).code
for n in range(3):
time.sleep(1)
except:
print("Initialised babel!")
INITIALISED = True
return babel.transform(code, {'presets': babelPresetEs2015}).code
if __name__ == '__main__':
print(js6_to_js5('obj={}; obj.x = function() {return () => this}'))
print()
print(js6_to_js5('const a = 1;'))
@@ -0,0 +1,6 @@
// run buildBabel in this folder to convert this code to python!
var babel = require("babel-core");
var babelPresetEs2015 = require("babel-preset-es2015");
Object.babelPresetEs2015 = babelPresetEs2015;
Object.babel = babel;
File diff suppressed because one or more lines are too long
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
# install all the dependencies and pack everything into one file babel_bundle.js (converted to es2015).
npm install babel-core babel-cli babel-preset-es2015 browserify
browserify babel.js -o babel_bundle.js -t [ babelify --presets [ es2015 ] ]
# translate babel_bundle.js using js2py -> generates babel.py
echo "Generating babel.py..."
python -c "import js2py;js2py.translate_file('babel_bundle.js', 'babel.py');"
rm babel_bundle.js
+267
View File
@@ -0,0 +1,267 @@
# coding=utf-8
from .translators import translate_js, DEFAULT_HEADER
from .es6 import js6_to_js5
import sys
import time
import json
import six
import os
import hashlib
import codecs
__all__ = [
'EvalJs', 'translate_js', 'import_js', 'eval_js', 'translate_file',
'eval_js6', 'translate_js6', 'run_file', 'disable_pyimport',
'get_file_contents', 'write_file_contents'
]
DEBUG = False
def disable_pyimport():
import pyjsparser.parser
pyjsparser.parser.ENABLE_PYIMPORT = False
def path_as_local(path):
if os.path.isabs(path):
return path
# relative to cwd
return os.path.join(os.getcwd(), path)
def import_js(path, lib_name, globals):
"""Imports from javascript source file.
globals is your globals()"""
with codecs.open(path_as_local(path), "r", "utf-8") as f:
js = f.read()
e = EvalJs()
e.execute(js)
var = e.context['var']
globals[lib_name] = var.to_python()
def get_file_contents(path_or_file):
if hasattr(path_or_file, 'read'):
js = path_or_file.read()
else:
with codecs.open(path_as_local(path_or_file), "r", "utf-8") as f:
js = f.read()
return js
def write_file_contents(path_or_file, contents):
if hasattr(path_or_file, 'write'):
path_or_file.write(contents)
else:
with open(path_as_local(path_or_file), 'w') as f:
f.write(contents)
def translate_file(input_path, output_path):
'''
Translates input JS file to python and saves the it to the output path.
It appends some convenience code at the end so that it is easy to import JS objects.
For example we have a file 'example.js' with: var a = function(x) {return x}
translate_file('example.js', 'example.py')
Now example.py can be easily importend and used:
>>> from example import example
>>> example.a(30)
30
'''
js = get_file_contents(input_path)
py_code = translate_js(js)
lib_name = os.path.basename(output_path).split('.')[0]
head = '__all__ = [%s]\n\n# Don\'t look below, you will not understand this Python code :) I don\'t.\n\n' % repr(
lib_name)
tail = '\n\n# Add lib to the module scope\n%s = var.to_python()' % lib_name
out = head + py_code + tail
write_file_contents(output_path, out)
def run_file(path_or_file, context=None):
''' Context must be EvalJS object. Runs given path as a JS program. Returns (eval_value, context).
'''
if context is None:
context = EvalJs()
if not isinstance(context, EvalJs):
raise TypeError('context must be the instance of EvalJs')
eval_value = context.eval(get_file_contents(path_or_file))
return eval_value, context
def eval_js(js):
"""Just like javascript eval. Translates javascript to python,
executes and returns python object.
js is javascript source code
EXAMPLE:
>>> import js2py
>>> add = js2py.eval_js('function add(a, b) {return a + b}')
>>> add(1, 2) + 3
6
>>> add('1', 2, 3)
u'12'
>>> add.constructor
function Function() { [python code] }
NOTE: For Js Number, String, Boolean and other base types returns appropriate python BUILTIN type.
For Js functions and objects, returns Python wrapper - basically behaves like normal python object.
If you really want to convert object to python dict you can use to_dict method.
"""
e = EvalJs()
return e.eval(js)
def eval_js6(js):
"""Just like eval_js but with experimental support for js6 via babel."""
return eval_js(js6_to_js5(js))
def translate_js6(js):
"""Just like translate_js but with experimental support for js6 via babel."""
return translate_js(js6_to_js5(js))
class EvalJs(object):
"""This class supports continuous execution of javascript under same context.
>>> js = EvalJs()
>>> js.execute('var a = 10;function f(x) {return x*x};')
>>> js.f(9)
81
>>> js.a
10
context is a python dict or object that contains python variables that should be available to JavaScript
For example:
>>> js = EvalJs({'a': 30})
>>> js.execute('var x = a')
>>> js.x
30
You can run interactive javascript console with console method!"""
def __init__(self, context={}):
self.__dict__['_context'] = {}
exec (DEFAULT_HEADER, self._context)
self.__dict__['_var'] = self._context['var'].to_python()
if not isinstance(context, dict):
try:
context = context.__dict__
except:
raise TypeError(
'context has to be either a dict or have __dict__ attr')
for k, v in six.iteritems(context):
setattr(self._var, k, v)
def execute(self, js=None, use_compilation_plan=False):
"""executes javascript js in current context
During initial execute() the converted js is cached for re-use. That means next time you
run the same javascript snippet you save many instructions needed to parse and convert the
js code to python code.
This cache causes minor overhead (a cache dicts is updated) but the Js=>Py conversion process
is typically expensive compared to actually running the generated python code.
Note that the cache is just a dict, it has no expiration or cleanup so when running this
in automated situations with vast amounts of snippets it might increase memory usage.
"""
try:
cache = self.__dict__['cache']
except KeyError:
cache = self.__dict__['cache'] = {}
hashkey = hashlib.md5(js.encode('utf-8')).digest()
try:
compiled = cache[hashkey]
except KeyError:
code = translate_js(
js, '', use_compilation_plan=use_compilation_plan)
compiled = cache[hashkey] = compile(code, '<EvalJS snippet>',
'exec')
exec (compiled, self._context)
def eval(self, expression, use_compilation_plan=False):
"""evaluates expression in current context and returns its value"""
code = 'PyJsEvalResult = eval(%s)' % json.dumps(expression)
self.execute(code, use_compilation_plan=use_compilation_plan)
return self['PyJsEvalResult']
def execute_debug(self, js):
"""executes javascript js in current context
as opposed to the (faster) self.execute method, you can use your regular debugger
to set breakpoints and inspect the generated python code
"""
code = translate_js(js, '')
# make sure you have a temp folder:
filename = 'temp' + os.sep + '_' + hashlib.md5(
code.encode("utf-8")).hexdigest() + '.py'
try:
with open(filename, mode='w') as f:
f.write(code)
with open(filename, "r") as f:
pyCode = compile(f.read(), filename, 'exec')
exec(pyCode, self._context)
except Exception as err:
raise err
finally:
os.remove(filename)
try:
os.remove(filename + 'c')
except:
pass
def eval_debug(self, expression):
"""evaluates expression in current context and returns its value
as opposed to the (faster) self.execute method, you can use your regular debugger
to set breakpoints and inspect the generated python code
"""
code = 'PyJsEvalResult = eval(%s)' % json.dumps(expression)
self.execute_debug(code)
return self['PyJsEvalResult']
def __getattr__(self, var):
return getattr(self._var, var)
def __getitem__(self, var):
return getattr(self._var, var)
def __setattr__(self, var, val):
return setattr(self._var, var, val)
def __setitem__(self, var, val):
return setattr(self._var, var, val)
def console(self):
"""starts to interact (starts interactive console) Something like code.InteractiveConsole"""
while True:
if six.PY2:
code = raw_input('>>> ')
else:
code = input('>>>')
try:
print(self.eval(code))
except KeyboardInterrupt:
break
except Exception as e:
import traceback
if DEBUG:
sys.stderr.write(traceback.format_exc())
else:
sys.stderr.write('EXCEPTION: ' + str(e) + '\n')
time.sleep(0.01)
#print x
if __name__ == '__main__':
#with open('C:\Users\Piotrek\Desktop\esprima.js', 'rb') as f:
# x = f.read()
e = EvalJs()
e.execute('square(x)')
#e.execute(x)
e.console()
@@ -0,0 +1,15 @@
from ..base import *
@Js
def console():
pass
@Js
def log():
print(arguments[0])
console.put('log', log)
console.put('debug', log)
console.put('info', log)
console.put('warn', log)
console.put('error', log)
@@ -0,0 +1,51 @@
from ..base import *
import inspect
try:
from js2py.translators.translator import translate_js
except:
pass
@Js
def Eval(code):
local_scope = inspect.stack()[3][0].f_locals['var']
global_scope = this.GlobalObject
# todo fix scope - we have to behave differently if called through variable other than eval
# we will use local scope (default)
globals()['var'] = local_scope
try:
py_code = translate_js(code.to_string().value, '')
except SyntaxError as syn_err:
raise MakeError('SyntaxError', str(syn_err))
lines = py_code.split('\n')
# a simple way to return value from eval. Will not work in complex cases.
has_return = False
for n in xrange(len(lines)):
line = lines[len(lines) - n - 1]
if line.strip():
if line.startswith(' '):
break
elif line.strip() == 'pass':
continue
elif any(
line.startswith(e)
for e in ['return ', 'continue ', 'break', 'raise ']):
break
else:
has_return = True
cand = 'EVAL_RESULT = (%s)\n' % line
try:
compile(cand, '', 'exec')
except SyntaxError:
break
lines[len(lines) - n - 1] = cand
py_code = '\n'.join(lines)
break
#print py_code
executor(py_code)
if has_return:
return globals()['EVAL_RESULT']
def executor(code):
exec (code, globals())
@@ -0,0 +1,176 @@
from ..base import *
from six.moves.urllib.parse import quote, unquote
RADIX_CHARS = {
'1': 1,
'0': 0,
'3': 3,
'2': 2,
'5': 5,
'4': 4,
'7': 7,
'6': 6,
'9': 9,
'8': 8,
'a': 10,
'c': 12,
'b': 11,
'e': 14,
'd': 13,
'g': 16,
'f': 15,
'i': 18,
'h': 17,
'k': 20,
'j': 19,
'm': 22,
'l': 21,
'o': 24,
'n': 23,
'q': 26,
'p': 25,
's': 28,
'r': 27,
'u': 30,
't': 29,
'w': 32,
'v': 31,
'y': 34,
'x': 33,
'z': 35,
'A': 10,
'C': 12,
'B': 11,
'E': 14,
'D': 13,
'G': 16,
'F': 15,
'I': 18,
'H': 17,
'K': 20,
'J': 19,
'M': 22,
'L': 21,
'O': 24,
'N': 23,
'Q': 26,
'P': 25,
'S': 28,
'R': 27,
'U': 30,
'T': 29,
'W': 32,
'V': 31,
'Y': 34,
'X': 33,
'Z': 35
}
@Js
def parseInt(string, radix):
string = string.to_string().value.lstrip()
sign = 1
if string and string[0] in ('+', '-'):
if string[0] == '-':
sign = -1
string = string[1:]
r = radix.to_int32()
strip_prefix = True
if r:
if r < 2 or r > 36:
return NaN
if r != 16:
strip_prefix = False
else:
r = 10
if strip_prefix:
if len(string) >= 2 and string[:2] in ('0x', '0X'):
string = string[2:]
r = 16
n = 0
num = 0
while n < len(string):
cand = RADIX_CHARS.get(string[n])
if cand is None or not cand < r:
break
num = cand + num * r
n += 1
if not n:
return NaN
return sign * num
@Js
def parseFloat(string):
string = string.to_string().value.strip()
sign = 1
if string and string[0] in ('+', '-'):
if string[0] == '-':
sign = -1
string = string[1:]
num = None
length = 1
max_len = None
failed = 0
while length <= len(string):
try:
num = float(string[:length])
max_len = length
failed = 0
except:
failed += 1
if failed > 4: # cant be a number anymore
break
length += 1
if num is None:
return NaN
return sign * float(string[:max_len])
@Js
def isNaN(number):
if number.to_number().is_nan():
return true
return false
@Js
def isFinite(number):
num = number.to_number()
if num.is_nan() or num.is_infinity():
return false
return true
# todo test them properly
@Js
def escape(text):
return quote(text.to_string().value)
@Js
def unescape(text):
return unquote(text.to_string().value)
@Js
def encodeURI(text):
return quote(text.to_string().value, safe='~@#$&()*!+=:;,.?/\'')
@Js
def decodeURI(text):
return unquote(text.to_string().value)
@Js
def encodeURIComponent(text):
return quote(text.to_string().value, safe='~()*!.\'')
@Js
def decodeURIComponent(text):
return unquote(text.to_string().value)
@@ -0,0 +1,929 @@
from __future__ import unicode_literals
import re
import datetime
from .desc import *
from .simplex import *
from .conversions import *
from pyjsparser import PyJsParser
import six
if six.PY2:
from itertools import izip
else:
izip = zip
def Type(obj):
return obj.TYPE
# 8.6.2
class PyJs(object):
TYPE = 'Object'
IS_CONSTRUCTOR = False
prototype = None
Class = None
extensible = True
value = None
own = {}
def get_member(self, unconverted_prop):
return self.get(to_string(unconverted_prop))
def put_member(self, unconverted_prop, val):
return self.put(to_string(unconverted_prop), val)
def get(self, prop):
assert type(prop) == unicode
cand = self.get_property(prop)
if cand is None:
return undefined
if is_data_descriptor(cand):
return cand['value']
if is_undefined(cand['get']):
return undefined
return cand['get'].call(self)
def get_own_property(self, prop):
assert type(prop) == unicode
# takes py returns py
return self.own.get(prop)
def get_property(self, prop):
assert type(prop) == unicode
# take py returns py
cand = self.get_own_property(prop)
if cand:
return cand
if self.prototype is not None:
return self.prototype.get_property(prop)
def put(self, prop, val, throw=False):
assert type(prop) == unicode
# takes py, returns none
if not self.can_put(prop):
if throw:
raise MakeError('TypeError', 'Could not define own property')
return
own_desc = self.get_own_property(prop)
if is_data_descriptor(own_desc):
self.own[prop]['value'] = val
return
desc = self.get_property(prop)
if is_accessor_descriptor(desc):
desc['set'].call(
self, (val, )) # calling setter on own or inherited element
else: # new property
self.own[prop] = {
'value': val,
'writable': True,
'configurable': True,
'enumerable': True
}
def can_put(self, prop): # to check
assert type(prop) == unicode, type(prop)
# takes py returns py
desc = self.get_own_property(prop)
if desc: # if we have this property
if is_accessor_descriptor(desc):
return is_callable(
desc['set']) # Check if setter method is defined
else: # data desc
return desc['writable']
if self.prototype is None:
return self.extensible
inherited = self.prototype.get_property(prop)
if inherited is None:
return self.extensible
if is_accessor_descriptor(inherited):
return not is_undefined(inherited['set'])
elif self.extensible:
return inherited['writable'] # weird...
return False
def has_property(self, prop):
assert type(prop) == unicode
# takes py returns Py
return self.get_property(prop) is not None
def delete(self, prop, throw=False):
assert type(prop) == unicode
# takes py, returns py
desc = self.get_own_property(prop)
if desc is None:
return True
if desc['configurable']:
del self.own[prop]
return True
if throw:
raise MakeError('TypeError', 'Could not define own property')
return False
def default_value(self, hint=None):
order = ('valueOf', 'toString')
if hint == 'String' or (hint is None and self.Class == 'Date'):
order = ('toString', 'valueOf')
for meth_name in order:
method = self.get(meth_name)
if method is not None and is_callable(method):
cand = method.call(self, ())
if is_primitive(cand):
return cand
raise MakeError('TypeError',
'Cannot convert object to primitive value')
def define_own_property(
self, prop, desc,
throw): # Internal use only. External through Object
assert type(prop) == unicode
# takes Py, returns Py
# prop must be a Py string. Desc is either a descriptor or accessor.
# Messy method - raw translation from Ecma spec to prevent any bugs. # todo check this
current = self.get_own_property(prop)
extensible = self.extensible
if not current: # We are creating a new OWN property
if not extensible:
if throw:
raise MakeError('TypeError',
'Could not define own property')
return False
# extensible must be True
if is_data_descriptor(desc) or is_generic_descriptor(desc):
DEFAULT_DATA_DESC = {
'value': undefined, # undefined
'writable': False,
'enumerable': False,
'configurable': False
}
DEFAULT_DATA_DESC.update(desc)
self.own[prop] = DEFAULT_DATA_DESC
else:
DEFAULT_ACCESSOR_DESC = {
'get': undefined, # undefined
'set': undefined, # undefined
'enumerable': False,
'configurable': False
}
DEFAULT_ACCESSOR_DESC.update(desc)
self.own[prop] = DEFAULT_ACCESSOR_DESC
return True
# therefore current exists!
if not desc or desc == current: # We don't need to change anything.
return True
configurable = current['configurable']
if not configurable: # Prevent changing params
if desc.get('configurable'):
if throw:
raise MakeError('TypeError',
'Could not define own property')
return False
if 'enumerable' in desc and desc['enumerable'] != current[
'enumerable']:
if throw:
raise MakeError('TypeError',
'Could not define own property')
return False
if is_generic_descriptor(desc):
pass
elif is_data_descriptor(current) != is_data_descriptor(desc):
# we want to change the current type of property
if not configurable:
if throw:
raise MakeError('TypeError',
'Could not define own property')
return False
if is_data_descriptor(current): # from data to setter
del current['value']
del current['writable']
current['set'] = undefined # undefined
current['get'] = undefined # undefined
else: # from setter to data
del current['set']
del current['get']
current['value'] = undefined # undefined
current['writable'] = False
elif is_data_descriptor(current) and is_data_descriptor(desc):
if not configurable:
if not current['writable'] and desc.get('writable'):
if throw:
raise MakeError('TypeError',
'Could not define own property')
return False
if not current['writable'] and 'value' in desc and current[
'value'] != desc['value']:
if throw:
raise MakeError('TypeError',
'Could not define own property')
return False
elif is_accessor_descriptor(current) and is_accessor_descriptor(desc):
if not configurable:
if 'set' in desc and desc['set'] != current['set']:
if throw:
raise MakeError('TypeError',
'Could not define own property')
return False
if 'get' in desc and desc['get'] != current['get']:
if throw:
raise MakeError('TypeError',
'Could not define own property')
return False
current.update(desc)
return True
def create(self, args, space):
'''Generally not a constructor, raise an error'''
raise MakeError('TypeError', '%s is not a constructor' % self.Class)
def get_member(
self, prop, space
): # general member getter, prop has to be unconverted prop. it is it can be any value
typ = type(self)
if typ not in PRIMITIVES: # most likely getter for object
return self.get_member(
prop
) # <- object can implement this to support faster prop getting. ex array.
elif typ == unicode: # then probably a String
if type(prop) == float and is_finite(prop):
index = int(prop)
if index == prop and 0 <= index < len(self):
return self[index]
s_prop = to_string(prop)
if s_prop == 'length':
return float(len(self))
elif s_prop.isdigit():
index = int(s_prop)
if 0 <= index < len(self):
return self[index]
# use standard string prototype
return space.StringPrototype.get(s_prop)
# maybe an index
elif typ == float:
# use standard number prototype
return space.NumberPrototype.get(to_string(prop))
elif typ == bool:
return space.BooleanPrototype.get(to_string(prop))
elif typ is UNDEFINED_TYPE:
raise MakeError('TypeError',
"Cannot read property '%s' of undefined" % prop)
elif typ is NULL_TYPE:
raise MakeError('TypeError',
"Cannot read property '%s' of null" % prop)
else:
raise RuntimeError('Unknown type! - ' + repr(typ))
def get_member_dot(self, prop, space):
# dot member getter, prop has to be unicode
typ = type(self)
if typ not in PRIMITIVES: # most likely getter for object
return self.get(prop)
elif typ == unicode: # then probably a String
if prop == 'length':
return float(len(self))
elif prop.isdigit():
index = int(prop)
if 0 <= index < len(self):
return self[index]
else:
# use standard string prototype
return space.StringPrototype.get(prop)
# maybe an index
elif typ == float:
# use standard number prototype
return space.NumberPrototype.get(prop)
elif typ == bool:
return space.BooleanPrototype.get(prop)
elif typ in (UNDEFINED_TYPE, NULL_TYPE):
raise MakeError('TypeError',
"Cannot read property '%s' of undefined" % prop)
else:
raise RuntimeError('Unknown type! - ' + repr(typ))
# Object
class PyJsObject(PyJs):
TYPE = 'Object'
Class = 'Object'
def __init__(self, prototype=None):
self.prototype = prototype
self.own = {}
def _init(self, props, vals):
i = 0
for prop, kind in props:
if prop in self.own: # just check... probably will not happen very often.
if is_data_descriptor(self.own[prop]):
if kind != 'i':
raise MakeError(
'SyntaxError',
'Invalid object initializer! Duplicate property name "%s"'
% prop)
else:
if kind == 'i' or (kind == 'g' and 'get' in self.own[prop]
) or (kind == 's'
and 'set' in self.own[prop]):
raise MakeError(
'SyntaxError',
'Invalid object initializer! Duplicate setter/getter of prop: "%s"'
% prop)
if kind == 'i': # init
self.own[prop] = {
'value': vals[i],
'writable': True,
'enumerable': True,
'configurable': True
}
elif kind == 'g': # get
self.define_own_property(prop, {
'get': vals[i],
'enumerable': True,
'configurable': True
}, False)
elif kind == 's': # get
self.define_own_property(prop, {
'get': vals[i],
'enumerable': True,
'configurable': True
}, False)
else:
raise RuntimeError(
'Invalid property kind - %s. Expected one of i, g, s.' %
repr(kind))
i += 1
def _set_props(self, prop_descs):
for prop, desc in six.iteritems(prop_descs):
self.define_own_property(prop, desc)
# Array
# todo Optimise Array - extremely slow due to index conversions from str to int and back etc.
# solution - make get and put methods callable with any type of prop and handle conversions from inside
# if not array then use to_string(prop). In array if prop is integer then just use it
# also consider removal of these stupid writable, enumerable etc for ints.
class PyJsArray(PyJs):
Class = 'Array'
def __init__(self, length, prototype=None):
self.prototype = prototype
self.own = {
'length': {
'value': float(length),
'writable': True,
'enumerable': False,
'configurable': False
}
}
def _init(self, elements):
for i, ele in enumerate(elements):
if ele is None: continue
self.own[unicode(i)] = {
'value': ele,
'writable': True,
'enumerable': True,
'configurable': True
}
def put(self, prop, val, throw=False):
assert type(val) != int
# takes py, returns none
if not self.can_put(prop):
if throw:
raise MakeError('TypeError', 'Could not define own property')
return
own_desc = self.get_own_property(prop)
if is_data_descriptor(own_desc):
self.define_own_property(prop, {'value': val}, False)
return
desc = self.get_property(prop)
if is_accessor_descriptor(desc):
desc['set'].call(
self, (val, )) # calling setter on own or inherited element
else: # new property
self.define_own_property(
prop, {
'value': val,
'writable': True,
'configurable': True,
'enumerable': True
}, False)
def define_own_property(self, prop, desc, throw):
assert type(desc.get('value')) != int
old_len_desc = self.get_own_property('length')
old_len = old_len_desc['value'] # value is js type so convert to py.
if prop == 'length':
if 'value' not in desc:
return PyJs.define_own_property(self, prop, desc, False)
new_len = to_uint32(desc['value'])
if new_len != to_number(desc['value']):
raise MakeError('RangeError', 'Invalid range!')
new_desc = dict((k, v) for k, v in six.iteritems(desc))
new_desc['value'] = float(new_len)
if new_len >= old_len:
return PyJs.define_own_property(self, prop, new_desc, False)
if not old_len_desc['writable']:
return False
if 'writable' not in new_desc or new_desc['writable'] == True:
new_writable = True
else:
new_writable = False
new_desc['writable'] = True
if not PyJs.define_own_property(self, prop, new_desc, False):
return False
if new_len < old_len:
# not very efficient for sparse arrays, so using different method for sparse:
if old_len > 30 * len(self.own):
for ele in self.own.keys():
if ele.isdigit() and int(ele) >= new_len:
if not self.delete(
ele
): # if failed to delete set len to current len and reject.
new_desc['value'] = old_len + 1.
if not new_writable:
new_desc['writable'] = False
PyJs.define_own_property(
self, prop, new_desc, False)
return False
old_len = new_len
else: # standard method
while new_len < old_len:
old_len -= 1
if not self.delete(
unicode(int(old_len))
): # if failed to delete set len to current len and reject.
new_desc['value'] = old_len + 1.
if not new_writable:
new_desc['writable'] = False
PyJs.define_own_property(self, prop, new_desc,
False)
return False
if not new_writable:
self.own['length']['writable'] = False
return True
elif prop.isdigit():
index = to_uint32(prop)
if index >= old_len and not old_len_desc['writable']:
return False
if not PyJs.define_own_property(self, prop, desc, False):
return False
if index >= old_len:
old_len_desc['value'] = index + 1.
return True
else:
return PyJs.define_own_property(self, prop, desc, False)
def to_list(self):
return [
self.get(str(e)) for e in xrange(self.get('length').to_uint32())
]
# database with compiled patterns. Js pattern -> Py pattern.
REGEXP_DB = {}
class PyJsRegExp(PyJs):
Class = 'RegExp'
def __init__(self, body, flags, prototype=None):
self.prototype = prototype
self.glob = True if 'g' in flags else False
self.ignore_case = re.IGNORECASE if 'i' in flags else 0
self.multiline = re.MULTILINE if 'm' in flags else 0
self.value = body
if (body, flags) in REGEXP_DB:
self.pat = REGEXP_DB[body, flags]
else:
comp = None
try:
# converting JS regexp pattern to Py pattern.
possible_fixes = [(u'[]', u'[\0]'), (u'[^]', u'[^\0]'),
(u'nofix1791', u'nofix1791')]
reg = self.value
for fix, rep in possible_fixes:
comp = PyJsParser()._interpret_regexp(reg, flags)
#print 'reg -> comp', reg, '->', comp
try:
self.pat = re.compile(
comp, self.ignore_case | self.multiline)
#print reg, '->', comp
break
except:
reg = reg.replace(fix, rep)
# print 'Fix', fix, '->', rep, '=', reg
else:
raise Exception()
REGEXP_DB[body, flags] = self.pat
except:
#print 'Invalid pattern...', self.value, comp
raise MakeError(
'SyntaxError',
'Invalid RegExp pattern: %s -> %s' % (repr(self.value),
repr(comp)))
# now set own properties:
self.own = {
'source': {
'value': self.value,
'enumerable': False,
'writable': False,
'configurable': False
},
'global': {
'value': self.glob,
'enumerable': False,
'writable': False,
'configurable': False
},
'ignoreCase': {
'value': bool(self.ignore_case),
'enumerable': False,
'writable': False,
'configurable': False
},
'multiline': {
'value': bool(self.multiline),
'enumerable': False,
'writable': False,
'configurable': False
},
'lastIndex': {
'value': 0.,
'enumerable': False,
'writable': True,
'configurable': False
}
}
def match(self, string, pos):
'''string is of course a py string'''
return self.pat.match(string, int(pos))
class PyJsError(PyJs):
Class = 'Error'
extensible = True
def __init__(self, message=None, prototype=None):
self.prototype = prototype
self.own = {}
if message is not None:
self.put('message', to_string(message))
self.own['message']['enumerable'] = False
class PyJsDate(PyJs):
Class = 'Date'
UTCToLocal = None # todo UTC to local should be imported!
def __init__(self, value, prototype=None):
self.value = value
self.own = {}
self.prototype = prototype
# todo fix this problematic datetime part
def to_local_dt(self):
return datetime.datetime.utcfromtimestamp(
self.UTCToLocal(self.value) // 1000)
def to_utc_dt(self):
return datetime.datetime.utcfromtimestamp(self.value // 1000)
def local_strftime(self, pattern):
if self.value is NaN:
return 'Invalid Date'
try:
dt = self.to_local_dt()
except:
raise MakeError(
'TypeError',
'unsupported date range. Will fix in future versions')
try:
return dt.strftime(pattern)
except:
raise MakeError(
'TypeError',
'Could not generate date string from this date (limitations of python.datetime)'
)
def utc_strftime(self, pattern):
if self.value is NaN:
return 'Invalid Date'
try:
dt = self.to_utc_dt()
except:
raise MakeError(
'TypeError',
'unsupported date range. Will fix in future versions')
try:
return dt.strftime(pattern)
except:
raise MakeError(
'TypeError',
'Could not generate date string from this date (limitations of python.datetime)'
)
# Scope class it will hold all the variables accessible to user
class Scope(PyJs):
Class = 'Global'
extensible = True
IS_CHILD_SCOPE = True
THIS_BINDING = None
space = None
exe = None
# todo speed up!
# in order to speed up this very important class the top scope should behave differently than
# child scopes, child scope should not have this property descriptor thing because they cant be changed anyway
# they are all confugurable= False
def __init__(self, scope, space, parent=None):
"""Doc"""
self.space = space
self.prototype = parent
if type(scope) is not dict:
assert parent is not None, 'You initialised the WITH_SCOPE without a parent scope.'
self.own = scope
self.is_with_scope = True
else:
self.is_with_scope = False
if parent is None:
# global, top level scope
self.own = {}
for k, v in six.iteritems(scope):
# set all the global items
self.define_own_property(
k, {
'value': v,
'configurable': False,
'writable': False,
'enumerable': False
}, False)
else:
# not global, less powerful but faster closure.
self.own = scope # simple dictionary which maps name directly to js object.
self.par = super(Scope, self)
self.stack = []
def register(self, var):
# registered keeps only global registered variables
if self.prototype is None:
# define in global scope
if var in self.own:
self.own[var]['configurable'] = False
else:
self.define_own_property(
var, {
'value': undefined,
'configurable': False,
'writable': True,
'enumerable': True
}, False)
elif var not in self.own:
# define in local scope since it has not been defined yet
self.own[var] = undefined # default value
def registers(self, vars):
"""register multiple variables"""
for var in vars:
self.register(var)
def put(self, var, val, throw=False):
if self.prototype is None:
desc = self.own.get(var) # global scope
if desc is None:
self.par.put(var, val, False)
else:
if desc['writable']: # todo consider getters/setters
desc['value'] = val
else:
if self.is_with_scope:
if self.own.has_property(var):
return self.own.put(var, val, throw=throw)
else:
return self.prototype.put(var, val)
# trying to put in local scope
# we dont know yet in which scope we should place this var
elif var in self.own:
self.own[var] = val
return val
else:
# try to put in the lower scope since we cant put in this one (var wasn't registered)
return self.prototype.put(var, val)
def get(self, var, throw=False):
if self.prototype is not None:
if self.is_with_scope:
cand = None if not self.own.has_property(
var) else self.own.get(var)
else:
# fast local scope
cand = self.own.get(var)
if cand is None:
return self.prototype.get(var, throw)
return cand
# slow, global scope
if var not in self.own:
# try in ObjectPrototype...
if var in self.space.ObjectPrototype.own:
return self.space.ObjectPrototype.get(var)
if throw:
raise MakeError('ReferenceError', '%s is not defined' % var)
return undefined
cand = self.own[var].get('value')
return cand if cand is not None else self.own[var]['get'].call(self)
def delete(self, var, throw=False):
if self.prototype is not None:
if self.is_with_scope:
if self.own.has_property(var):
return self.own.delete(var)
elif var in self.own:
return False
return self.prototype.delete(var)
# we are in global scope here. Must exist and be configurable to delete
if var not in self.own:
# this var does not exist, why do you want to delete it???
return True
if self.own[var]['configurable']:
del self.own[var]
return True
# not configurable, cant delete
return False
def get_new_arguments_obj(args, space):
obj = space.NewObject()
obj.Class = 'Arguments'
obj.define_own_property(
'length', {
'value': float(len(args)),
'writable': True,
'enumerable': False,
'configurable': True
}, False)
for i, e in enumerate(args):
obj.put(unicode(i), e)
return obj
#Function
class PyJsFunction(PyJs):
Class = 'Function'
source = '{ [native code] }'
IS_CONSTRUCTOR = True
def __init__(self,
code,
ctx,
params,
name,
space,
is_declaration,
definitions,
prototype=None):
self.prototype = prototype
self.own = {}
self.code = code
if type(
self.code
) == int: # just a label pointing to the beginning of the code.
self.is_native = False
else:
self.is_native = True # python function
self.ctx = ctx
self.params = params
self.arguments_in_params = 'arguments' in params
self.definitions = definitions
# todo remove this check later
for p in params:
assert p in self.definitions
self.name = name
self.space = space
self.is_declaration = is_declaration
#set own property length to the number of arguments
self.own['length'] = {
'value': float(len(params)),
'writable': False,
'enumerable': False,
'configurable': False
}
if name:
self.own['name'] = {
'value': name,
'writable': False,
'enumerable': False,
'configurable': True
}
if not self.is_native: # set prototype for user defined functions
# constructor points to this function
proto = space.NewObject()
proto.own['constructor'] = {
'value': self,
'writable': True,
'enumerable': False,
'configurable': True
}
self.own['prototype'] = {
'value': proto,
'writable': True,
'enumerable': False,
'configurable': False
}
# todo set up throwers on callee and arguments if in strict mode
def call(self, this, args=()):
''' Dont use this method from inside bytecode to call other bytecode. '''
if self.is_native:
_args = SpaceTuple(
args
) # we have to do that unfortunately to pass all the necessary info to the funcs
_args.space = self.space
return self.code(
this, _args
) # must return valid js object - undefined, null, float, unicode, bool, or PyJs
else:
return self.space.exe._call(self, this,
args) # will run inside bytecode
def has_instance(self, other):
# I am not sure here so instanceof may not work lol.
if not is_object(other):
return False
proto = self.get('prototype')
if not is_object(proto):
raise MakeError(
'TypeError',
'Function has non-object prototype in instanceof check')
while True:
other = other.prototype
if not other: # todo make sure that the condition is not None or null
return False
if other is proto:
return True
def create(self, args, space):
proto = self.get('prototype')
if not is_object(proto):
proto = space.ObjectPrototype
new = PyJsObject(prototype=proto)
res = self.call(new, args)
if is_object(res):
return res
return new
def _generate_my_context(self, this, args):
my_ctx = Scope(
dict(izip(self.params, args)), self.space, parent=self.ctx)
my_ctx.registers(self.definitions)
my_ctx.THIS_BINDING = this
if not self.arguments_in_params:
my_ctx.own['arguments'] = get_new_arguments_obj(args, self.space)
if not self.is_declaration and self.name and self.name not in my_ctx.own:
my_ctx.own[
self.
name] = self # this should be immutable binding but come on!
return my_ctx
class SpaceTuple:
def __init__(self, tup):
self.tup = tup
def __len__(self):
return len(self.tup)
def __getitem__(self, item):
return self.tup[item]
def __iter__(self):
return iter(self.tup)
@@ -0,0 +1,753 @@
from .code import Code
from .simplex import MakeError
from .opcodes import *
from .operations import *
from .trans_utils import *
SPECIAL_IDENTIFIERS = {'true', 'false', 'this'}
class ByteCodeGenerator:
def __init__(self, exe):
self.exe = exe
self.declared_continue_labels = {}
self.declared_break_labels = {}
self.implicit_breaks = []
self.implicit_continues = []
self.declared_vars = []
self.function_declaration_tape = []
self.states = []
def record_state(self):
self.states.append(
(self.declared_continue_labels, self.declared_break_labels,
self.implicit_breaks, self.implicit_continues, self.declared_vars,
self.function_declaration_tape))
self.declared_continue_labels, self.declared_break_labels, \
self.implicit_breaks, self.implicit_continues, \
self.declared_vars, self.function_declaration_tape = {}, {}, [], [], [], []
def restore_state(self):
self.declared_continue_labels, self.declared_break_labels, \
self.implicit_breaks, self.implicit_continues, \
self.declared_vars, self.function_declaration_tape = self.states.pop()
def ArrayExpression(self, elements, **kwargs):
for e in elements:
if e is None:
self.emit('LOAD_NONE')
else:
self.emit(e)
self.emit('LOAD_ARRAY', len(elements))
def AssignmentExpression(self, operator, left, right, **kwargs):
operator = operator[:-1]
if left['type'] == 'MemberExpression':
self.emit(left['object'])
if left['computed']:
self.emit(left['property'])
self.emit(right)
if operator:
self.emit('STORE_MEMBER_OP', operator)
else:
self.emit('STORE_MEMBER')
else:
self.emit(right)
if operator:
self.emit('STORE_MEMBER_DOT_OP', left['property']['name'],
operator)
else:
self.emit('STORE_MEMBER_DOT', left['property']['name'])
elif left['type'] == 'Identifier':
if left['name'] in SPECIAL_IDENTIFIERS:
raise MakeError('SyntaxError',
'Invalid left-hand side in assignment')
self.emit(right)
if operator:
self.emit('STORE_OP', left['name'], operator)
else:
self.emit('STORE', left['name'])
else:
raise MakeError('SyntaxError',
'Invalid left-hand side in assignment')
def BinaryExpression(self, operator, left, right, **kwargs):
self.emit(left)
self.emit(right)
self.emit('BINARY_OP', operator)
def BlockStatement(self, body, **kwargs):
self._emit_statement_list(body)
def BreakStatement(self, label, **kwargs):
if label is None:
self.emit('JUMP', self.implicit_breaks[-1])
else:
label = label.get('name')
if label not in self.declared_break_labels:
raise MakeError('SyntaxError',
'Undefined label \'%s\'' % label)
else:
self.emit('JUMP', self.declared_break_labels[label])
def CallExpression(self, callee, arguments, **kwargs):
if callee['type'] == 'MemberExpression':
self.emit(callee['object'])
if callee['computed']:
self.emit(callee['property'])
if arguments:
for e in arguments:
self.emit(e)
self.emit('LOAD_N_TUPLE', len(arguments))
self.emit('CALL_METHOD')
else:
self.emit('CALL_METHOD_NO_ARGS')
else:
prop_name = to_key(callee['property'])
if arguments:
for e in arguments:
self.emit(e)
self.emit('LOAD_N_TUPLE', len(arguments))
self.emit('CALL_METHOD_DOT', prop_name)
else:
self.emit('CALL_METHOD_DOT_NO_ARGS', prop_name)
else:
self.emit(callee)
if arguments:
for e in arguments:
self.emit(e)
self.emit('LOAD_N_TUPLE', len(arguments))
self.emit('CALL')
else:
self.emit('CALL_NO_ARGS')
def ClassBody(self, body, **kwargs):
raise NotImplementedError('Not available in ECMA 5.1')
def ClassDeclaration(self, id, superClass, body, **kwargs):
raise NotImplementedError('Not available in ECMA 5.1')
def ClassExpression(self, id, superClass, body, **kwargs):
raise NotImplementedError('Classes not available in ECMA 5.1')
def ConditionalExpression(self, test, consequent, alternate, **kwargs):
alt = self.exe.get_new_label()
end = self.exe.get_new_label()
# ?
self.emit(test)
self.emit('JUMP_IF_FALSE', alt)
# first val
self.emit(consequent)
self.emit('JUMP', end)
# second val
self.emit('LABEL', alt)
self.emit(alternate)
# end of ?: statement
self.emit('LABEL', end)
def ContinueStatement(self, label, **kwargs):
if label is None:
self.emit('JUMP', self.implicit_continues[-1])
else:
label = label.get('name')
if label not in self.declared_continue_labels:
raise MakeError('SyntaxError',
'Undefined label \'%s\'' % label)
else:
self.emit('JUMP', self.declared_continue_labels[label])
def DebuggerStatement(self, **kwargs):
self.EmptyStatement(**kwargs)
def DoWhileStatement(self, body, test, **kwargs):
continue_label = self.exe.get_new_label()
break_label = self.exe.get_new_label()
initial_do = self.exe.get_new_label()
self.emit('JUMP', initial_do)
self.emit('LABEL', continue_label)
self.emit(test)
self.emit('JUMP_IF_FALSE', break_label)
self.emit('LABEL', initial_do)
# translate the body, remember to add and afterwards remove implicit break/continue labels
self.implicit_continues.append(continue_label)
self.implicit_breaks.append(break_label)
self.emit(body)
self.implicit_continues.pop()
self.implicit_breaks.pop()
self.emit('JUMP', continue_label) # loop back
self.emit('LABEL', break_label)
def EmptyStatement(self, **kwargs):
# do nothing
pass
def ExpressionStatement(self, expression, **kwargs):
# change the final stack value
# pop the previous value and execute expression
self.emit('POP')
self.emit(expression)
def ForStatement(self, init, test, update, body, **kwargs):
continue_label = self.exe.get_new_label()
break_label = self.exe.get_new_label()
first_start = self.exe.get_new_label()
if init is not None:
self.emit(init)
if init['type'] != 'VariableDeclaration':
self.emit('POP')
# skip first update and go straight to test
self.emit('JUMP', first_start)
self.emit('LABEL', continue_label)
if update:
self.emit(update)
self.emit('POP')
self.emit('LABEL', first_start)
if test:
self.emit(test)
self.emit('JUMP_IF_FALSE', break_label)
# translate the body, remember to add and afterwards to remove implicit break/continue labels
self.implicit_continues.append(continue_label)
self.implicit_breaks.append(break_label)
self.emit(body)
self.implicit_continues.pop()
self.implicit_breaks.pop()
self.emit('JUMP', continue_label) # loop back
self.emit('LABEL', break_label)
def ForInStatement(self, left, right, body, **kwargs):
# prepare the needed labels
body_start_label = self.exe.get_new_label()
continue_label = self.exe.get_new_label()
break_label = self.exe.get_new_label()
# prepare the name
if left['type'] == 'VariableDeclaration':
if len(left['declarations']) != 1:
raise MakeError(
'SyntaxError',
' Invalid left-hand side in for-in loop: Must have a single binding.'
)
self.emit(left)
name = left['declarations'][0]['id']['name']
elif left['type'] == 'Identifier':
name = left['name']
else:
raise MakeError('SyntaxError',
'Invalid left-hand side in for-loop')
# prepare the iterable
self.emit(right)
# emit ForIn Opcode
self.emit('FOR_IN', name, body_start_label, continue_label,
break_label)
# a special continue position
self.emit('LABEL', continue_label)
self.emit('NOP')
self.emit('LABEL', body_start_label)
self.implicit_continues.append(continue_label)
self.implicit_breaks.append(break_label)
self.emit('LOAD_UNDEFINED')
self.emit(body)
self.implicit_continues.pop()
self.implicit_breaks.pop()
self.emit('NOP')
self.emit('LABEL', break_label)
self.emit('NOP')
def FunctionDeclaration(self, id, params, defaults, body, **kwargs):
if defaults:
raise NotImplementedError('Defaults not available in ECMA 5.1')
# compile function
self.record_state(
) # cleans translator state and appends it to the stack so that it can be later restored
function_start = self.exe.get_new_label()
function_declarations = self.exe.get_new_label()
declarations_done = self.exe.get_new_label(
) # put jump to this place at the and of function tape!
function_end = self.exe.get_new_label()
# skip the function if encountered externally
self.emit('JUMP', function_end)
self.emit('LABEL', function_start)
# call is made with empty stack so load undefined to fill it
self.emit('LOAD_UNDEFINED')
# declare all functions
self.emit('JUMP', function_declarations)
self.emit('LABEL', declarations_done)
self.function_declaration_tape.append(LABEL(function_declarations))
self.emit(body)
self.ReturnStatement(None)
self.function_declaration_tape.append(JUMP(declarations_done))
self.exe.tape.extend(self.function_declaration_tape)
self.emit('LABEL', function_end)
declared_vars = self.declared_vars
self.restore_state()
# create function object and append to stack
name = id.get('name')
assert name is not None
self.declared_vars.append(name)
self.function_declaration_tape.append(
LOAD_FUNCTION(function_start, tuple(p['name'] for p in params),
name, True, tuple(declared_vars)))
self.function_declaration_tape.append(STORE(name))
self.function_declaration_tape.append(POP())
def FunctionExpression(self, id, params, defaults, body, **kwargs):
if defaults:
raise NotImplementedError('Defaults not available in ECMA 5.1')
# compile function
self.record_state(
) # cleans translator state and appends it to the stack so that it can be later restored
function_start = self.exe.get_new_label()
function_declarations = self.exe.get_new_label()
declarations_done = self.exe.get_new_label(
) # put jump to this place at the and of function tape!
function_end = self.exe.get_new_label()
# skip the function if encountered externally
self.emit('JUMP', function_end)
self.emit('LABEL', function_start)
# call is made with empty stack so load undefined to fill it
self.emit('LOAD_UNDEFINED')
# declare all functions
self.emit('JUMP', function_declarations)
self.emit('LABEL', declarations_done)
self.function_declaration_tape.append(LABEL(function_declarations))
self.emit(body)
self.ReturnStatement(None)
self.function_declaration_tape.append(JUMP(declarations_done))
self.exe.tape.extend(self.function_declaration_tape)
self.emit('LABEL', function_end)
declared_vars = self.declared_vars
self.restore_state()
# create function object and append to stack
name = id.get('name') if id else None
self.emit('LOAD_FUNCTION', function_start,
tuple(p['name'] for p in params), name, False,
tuple(declared_vars))
def Identifier(self, name, **kwargs):
if name == 'true':
self.emit('LOAD_BOOLEAN', 1)
elif name == 'false':
self.emit('LOAD_BOOLEAN', 0)
elif name == 'undefined':
self.emit('LOAD_UNDEFINED')
else:
self.emit('LOAD', name)
def IfStatement(self, test, consequent, alternate, **kwargs):
alt = self.exe.get_new_label()
end = self.exe.get_new_label()
# if
self.emit(test)
self.emit('JUMP_IF_FALSE', alt)
# consequent
self.emit(consequent)
self.emit('JUMP', end)
# alternate
self.emit('LABEL', alt)
if alternate is not None:
self.emit(alternate)
# end of if statement
self.emit('LABEL', end)
def LabeledStatement(self, label, body, **kwargs):
label = label['name']
if body['type'] in ('WhileStatement', 'DoWhileStatement',
'ForStatement', 'ForInStatement'):
# Continue label available... Simply take labels defined by the loop.
# It is important that they request continue label first
self.declared_continue_labels[label] = self.exe._label_count + 1
self.declared_break_labels[label] = self.exe._label_count + 2
self.emit(body)
del self.declared_break_labels[label]
del self.declared_continue_labels[label]
else:
# only break label available
lbl = self.exe.get_new_label()
self.declared_break_labels[label] = lbl
self.emit(body)
self.emit('LABEL', lbl)
del self.declared_break_labels[label]
def Literal(self, value, **kwargs):
if value is None:
self.emit('LOAD_NULL')
elif isinstance(value, bool):
self.emit('LOAD_BOOLEAN', int(value))
elif isinstance(value, basestring):
self.emit('LOAD_STRING', unicode(value))
elif isinstance(value, (float, int, long)):
self.emit('LOAD_NUMBER', float(value))
elif isinstance(value, tuple):
self.emit('LOAD_REGEXP', *value)
else:
raise RuntimeError('Unsupported literal')
def LogicalExpression(self, left, right, operator, **kwargs):
end = self.exe.get_new_label()
if operator == '&&':
# AND
self.emit(left)
self.emit('JUMP_IF_FALSE_WITHOUT_POP', end)
self.emit('POP')
self.emit(right)
self.emit('LABEL', end)
elif operator == '||':
# OR
self.emit(left)
self.emit('JUMP_IF_TRUE_WITHOUT_POP', end)
self.emit('POP')
self.emit(right)
self.emit('LABEL', end)
else:
raise RuntimeError("Unknown logical expression: %s" % operator)
def MemberExpression(self, computed, object, property, **kwargs):
if computed:
self.emit(object)
self.emit(property)
self.emit('LOAD_MEMBER')
else:
self.emit(object)
self.emit('LOAD_MEMBER_DOT', property['name'])
def NewExpression(self, callee, arguments, **kwargs):
self.emit(callee)
if arguments:
n = len(arguments)
for e in arguments:
self.emit(e)
self.emit('LOAD_N_TUPLE', n)
self.emit('NEW')
else:
self.emit('NEW_NO_ARGS')
def ObjectExpression(self, properties, **kwargs):
data = []
for prop in properties:
self.emit(prop['value'])
if prop['computed']:
raise NotImplementedError(
'ECMA 5.1 does not support computed object properties!')
data.append((to_key(prop['key']), prop['kind'][0]))
self.emit('LOAD_OBJECT', tuple(data))
def Program(self, body, **kwargs):
old_tape_len = len(self.exe.tape)
self.emit('LOAD_UNDEFINED')
self.emit(body)
# add function tape !
self.exe.tape = self.exe.tape[:old_tape_len] + self.function_declaration_tape + self.exe.tape[old_tape_len:]
def Pyimport(self, imp, **kwargs):
raise NotImplementedError(
'Not available for bytecode interpreter yet, use the Js2Py translator.'
)
def Property(self, kind, key, computed, value, method, shorthand,
**kwargs):
raise NotImplementedError('Not available in ECMA 5.1')
def RestElement(self, argument, **kwargs):
raise NotImplementedError('Not available in ECMA 5.1')
def ReturnStatement(self, argument, **kwargs):
self.emit('POP') # pop result of expression statements
if argument is None:
self.emit('LOAD_UNDEFINED')
else:
self.emit(argument)
self.emit('RETURN')
def SequenceExpression(self, expressions, **kwargs):
for e in expressions:
self.emit(e)
self.emit('POP')
del self.exe.tape[-1]
def SwitchCase(self, test, consequent, **kwargs):
raise NotImplementedError('Already implemented in SwitchStatement')
def SwitchStatement(self, discriminant, cases, **kwargs):
self.emit(discriminant)
labels = [self.exe.get_new_label() for case in cases]
tests = [case['test'] for case in cases]
consequents = [case['consequent'] for case in cases]
end_of_switch = self.exe.get_new_label()
# translate test cases
for test, label in zip(tests, labels):
if test is not None:
self.emit(test)
self.emit('JUMP_IF_EQ', label)
else:
self.emit('POP')
self.emit('JUMP', label)
# this will be executed if none of the cases worked
self.emit('POP')
self.emit('JUMP', end_of_switch)
# translate consequents
self.implicit_breaks.append(end_of_switch)
for consequent, label in zip(consequents, labels):
self.emit('LABEL', label)
self._emit_statement_list(consequent)
self.implicit_breaks.pop()
self.emit('LABEL', end_of_switch)
def ThisExpression(self, **kwargs):
self.emit('LOAD_THIS')
def ThrowStatement(self, argument, **kwargs):
# throw with the empty stack
self.emit('POP')
self.emit(argument)
self.emit('THROW')
def TryStatement(self, block, handler, finalizer, **kwargs):
try_label = self.exe.get_new_label()
catch_label = self.exe.get_new_label()
finally_label = self.exe.get_new_label()
end_label = self.exe.get_new_label()
self.emit('JUMP', end_label)
# try block
self.emit('LABEL', try_label)
self.emit('LOAD_UNDEFINED')
self.emit(block)
self.emit(
'NOP'
) # needed to distinguish from break/continue vs some internal jumps
# catch block
self.emit('LABEL', catch_label)
self.emit('LOAD_UNDEFINED')
if handler:
self.emit(handler['body'])
self.emit('NOP')
# finally block
self.emit('LABEL', finally_label)
self.emit('LOAD_UNDEFINED')
if finalizer:
self.emit(finalizer)
self.emit('NOP')
self.emit('LABEL', end_label)
# give life to the code
self.emit('TRY_CATCH_FINALLY', try_label, catch_label,
handler['param']['name'] if handler else None, finally_label,
bool(finalizer), end_label)
def UnaryExpression(self, operator, argument, **kwargs):
if operator == 'typeof' and argument[
'type'] == 'Identifier': # todo fix typeof
self.emit('TYPEOF', argument['name'])
elif operator == 'delete':
if argument['type'] == 'MemberExpression':
self.emit(argument['object'])
if argument['property']['type'] == 'Identifier':
self.emit('LOAD_STRING',
unicode(argument['property']['name']))
else:
self.emit(argument['property'])
self.emit('DELETE_MEMBER')
elif argument['type'] == 'Identifier':
self.emit('DELETE', argument['name'])
else:
self.emit('LOAD_BOOLEAN', 1)
elif operator in UNARY_OPERATIONS:
self.emit(argument)
self.emit('UNARY_OP', operator)
else:
raise MakeError('SyntaxError',
'Unknown unary operator %s' % operator)
def UpdateExpression(self, operator, argument, prefix, **kwargs):
incr = int(operator == "++")
post = int(not prefix)
if argument['type'] == 'MemberExpression':
if argument['computed']:
self.emit(argument['object'])
self.emit(argument['property'])
self.emit('POSTFIX_MEMBER', post, incr)
else:
self.emit(argument['object'])
name = to_key(argument['property'])
self.emit('POSTFIX_MEMBER_DOT', post, incr, name)
elif argument['type'] == 'Identifier':
name = to_key(argument)
self.emit('POSTFIX', post, incr, name)
else:
raise MakeError('SyntaxError',
'Invalid left-hand side in assignment')
def VariableDeclaration(self, declarations, kind, **kwargs):
if kind != 'var':
raise NotImplementedError(
'Only var variable declaration is supported by ECMA 5.1')
for d in declarations:
self.emit(d)
def LexicalDeclaration(self, declarations, kind, **kwargs):
raise NotImplementedError('Not supported by ECMA 5.1')
def VariableDeclarator(self, id, init, **kwargs):
name = id['name']
if name in SPECIAL_IDENTIFIERS:
raise MakeError('Invalid left-hand side in assignment')
self.declared_vars.append(name)
if init is not None:
self.emit(init)
self.emit('STORE', name)
self.emit('POP')
def WhileStatement(self, test, body, **kwargs):
continue_label = self.exe.get_new_label()
break_label = self.exe.get_new_label()
self.emit('LABEL', continue_label)
self.emit(test)
self.emit('JUMP_IF_FALSE', break_label)
# translate the body, remember to add and afterwards remove implicit break/continue labels
self.implicit_continues.append(continue_label)
self.implicit_breaks.append(break_label)
self.emit(body)
self.implicit_continues.pop()
self.implicit_breaks.pop()
self.emit('JUMP', continue_label) # loop back
self.emit('LABEL', break_label)
def WithStatement(self, object, body, **kwargs):
beg_label = self.exe.get_new_label()
end_label = self.exe.get_new_label()
# scope
self.emit(object)
# now the body
self.emit('JUMP', end_label)
self.emit('LABEL', beg_label)
self.emit('LOAD_UNDEFINED')
self.emit(body)
self.emit('NOP')
self.emit('LABEL', end_label)
# with statement implementation
self.emit('WITH', beg_label, end_label)
def _emit_statement_list(self, statements):
for statement in statements:
self.emit(statement)
def emit(self, what, *args):
''' what can be either name of the op, or node, or a list of statements.'''
if isinstance(what, basestring):
return self.exe.emit(what, *args)
elif isinstance(what, list):
self._emit_statement_list(what)
else:
return getattr(self, what['type'])(**what)
import os, codecs
def path_as_local(path):
if os.path.isabs(path):
return path
# relative to cwd
return os.path.join(os.getcwd(), path)
def get_file_contents(path_or_file):
if hasattr(path_or_file, 'read'):
js = path_or_file.read()
else:
with codecs.open(path_as_local(path_or_file), "r", "utf-8") as f:
js = f.read()
return js
def main():
from space import Space
import fill_space
from pyjsparser import parse
import json
a = ByteCodeGenerator(Code())
s = Space()
fill_space.fill_space(s, a)
a.exe.space = s
s.exe = a.exe
con = get_file_contents('internals/esprima.js')
d = parse(con + (
''';JSON.stringify(exports.parse(%s), 4, 4)''' % json.dumps(con)))
# d = parse('''
# function x(n) {
# log(n)
# return x(n+1)
# }
# x(0)
# ''')
# var v = 333333;
# while (v) {
# v--
#
# }
a.emit(d)
print(a.declared_vars)
print(a.exe.tape)
print(len(a.exe.tape))
a.exe.compile()
def log(this, args):
print(args[0])
return 999
print(a.exe.run(a.exe.space.GlobalObj))
if __name__ == '__main__':
main()
@@ -0,0 +1,227 @@
from .opcodes import *
from .space import *
from .base import *
class Code:
'''Can generate, store and run sequence of ops representing js code'''
def __init__(self, is_strict=False, debug_mode=False):
self.tape = []
self.compiled = False
self.label_locs = None
self.is_strict = is_strict
self.debug_mode = debug_mode
self.contexts = []
self.current_ctx = None
self.return_locs = []
self._label_count = 0
self.label_locs = None
# useful references
self.GLOBAL_THIS = None
self.space = None
# dbg
self.ctx_depth = 0
def get_new_label(self):
self._label_count += 1
return self._label_count
def emit(self, op_code, *args):
''' Adds op_code with specified args to tape '''
self.tape.append(OP_CODES[op_code](*args))
def compile(self, start_loc=0):
''' Records locations of labels and compiles the code '''
self.label_locs = {} if self.label_locs is None else self.label_locs
loc = start_loc
while loc < len(self.tape):
if type(self.tape[loc]) == LABEL:
self.label_locs[self.tape[loc].num] = loc
del self.tape[loc]
continue
loc += 1
self.compiled = True
def _call(self, func, this, args):
''' Calls a bytecode function func
NOTE: use !ONLY! when calling functions from native methods! '''
assert not func.is_native
# fake call - the the runner to return to the end of the file
old_contexts = self.contexts
old_return_locs = self.return_locs
old_curr_ctx = self.current_ctx
self.contexts = [FakeCtx()]
self.return_locs = [len(self.tape)] # target line after return
# prepare my ctx
my_ctx = func._generate_my_context(this, args)
self.current_ctx = my_ctx
# execute dunction
ret = self.run(my_ctx, starting_loc=self.label_locs[func.code])
# bring back old execution
self.current_ctx = old_curr_ctx
self.contexts = old_contexts
self.return_locs = old_return_locs
return ret
def execute_fragment_under_context(self, ctx, start_label, end_label):
''' just like run but returns if moved outside of the specified fragment
# 4 different exectution results
# 0=normal, 1=return, 2=jump_outside, 3=errors
# execute_fragment_under_context returns:
# (return_value, typ, return_value/jump_loc/py_error)
# IMPARTANT: It is guaranteed that the length of the ctx.stack is unchanged.
'''
old_curr_ctx = self.current_ctx
self.ctx_depth += 1
old_stack_len = len(ctx.stack)
old_ret_len = len(self.return_locs)
old_ctx_len = len(self.contexts)
try:
self.current_ctx = ctx
return self._execute_fragment_under_context(
ctx, start_label, end_label)
except JsException as err:
if self.debug_mode:
self._on_fragment_exit("js errors")
# undo the things that were put on the stack (if any) to ensure a proper error recovery
del ctx.stack[old_stack_len:]
del self.return_locs[old_ret_len:]
del self.contexts[old_ctx_len :]
return undefined, 3, err
finally:
self.ctx_depth -= 1
self.current_ctx = old_curr_ctx
assert old_stack_len == len(ctx.stack)
def _get_dbg_indent(self):
return self.ctx_depth * ' '
def _on_fragment_exit(self, mode):
print(self._get_dbg_indent() + 'ctx exit (%s)' % mode)
def _execute_fragment_under_context(self, ctx, start_label, end_label):
start, end = self.label_locs[start_label], self.label_locs[end_label]
initial_len = len(ctx.stack)
loc = start
entry_level = len(self.contexts)
# for e in self.tape[start:end]:
# print e
if self.debug_mode:
print(self._get_dbg_indent() + 'ctx entry (from:%d, to:%d)' % (start, end))
while loc < len(self.tape):
if len(self.contexts) == entry_level and loc >= end:
if self.debug_mode:
self._on_fragment_exit('normal')
assert loc == end
delta_stack = len(ctx.stack) - initial_len
assert delta_stack == +1, 'Stack change must be equal to +1! got %d' % delta_stack
return ctx.stack.pop(), 0, None # means normal return
# execute instruction
if self.debug_mode:
print(self._get_dbg_indent() + str(loc), self.tape[loc])
status = self.tape[loc].eval(ctx)
# check status for special actions
if status is not None:
if type(status) == int: # jump to label
loc = self.label_locs[status]
if len(self.contexts) == entry_level:
# check if jumped outside of the fragment and break if so
if not start <= loc < end:
if self.debug_mode:
self._on_fragment_exit('jump outside loc:%d label:%d' % (loc, status))
delta_stack = len(ctx.stack) - initial_len
assert delta_stack == +1, 'Stack change must be equal to +1! got %d' % delta_stack
return ctx.stack.pop(), 2, status # jump outside
continue
elif len(status) == 2: # a call or a return!
# call: (new_ctx, func_loc_label_num)
if status[0] is not None:
# append old state to the stack
self.contexts.append(ctx)
self.return_locs.append(loc + 1)
# set new state
loc = self.label_locs[status[1]]
ctx = status[0]
self.current_ctx = ctx
continue
# return: (None, None)
else:
if len(self.contexts) == entry_level:
if self.debug_mode:
self._on_fragment_exit('return')
delta_stack = len(ctx.stack) - initial_len
assert delta_stack == +1, 'Stack change must be equal to +1! got %d' % delta_stack
return undefined, 1, ctx.stack.pop(
) # return signal
return_value = ctx.stack.pop()
ctx = self.contexts.pop()
self.current_ctx = ctx
ctx.stack.append(return_value)
loc = self.return_locs.pop()
continue
# next instruction
loc += 1
if self.debug_mode:
self._on_fragment_exit('internal error - unexpected end of tape, will crash')
assert False, 'Remember to add NOP at the end!'
def run(self, ctx, starting_loc=0):
loc = starting_loc
self.current_ctx = ctx
while loc < len(self.tape):
# execute instruction
if self.debug_mode:
print(loc, self.tape[loc])
status = self.tape[loc].eval(ctx)
# check status for special actions
if status is not None:
if type(status) == int: # jump to label
loc = self.label_locs[status]
continue
elif len(status) == 2: # a call or a return!
# call: (new_ctx, func_loc_label_num)
if status[0] is not None:
# append old state to the stack
self.contexts.append(ctx)
self.return_locs.append(loc + 1)
# set new state
loc = self.label_locs[status[1]]
ctx = status[0]
self.current_ctx = ctx
continue
# return: (None, None)
else:
return_value = ctx.stack.pop()
ctx = self.contexts.pop()
self.current_ctx = ctx
ctx.stack.append(return_value)
loc = self.return_locs.pop()
continue
# next instruction
loc += 1
assert len(ctx.stack) == 1, ctx.stack
return ctx.stack.pop()
class FakeCtx(object):
def __init__(self):
self.stack = []
@@ -0,0 +1 @@
__author__ = 'Piotr Dabkowski'
@@ -0,0 +1,28 @@
from ..conversions import *
from ..func_utils import *
def Array(this, args):
return ArrayConstructor(args, args.space)
def ArrayConstructor(args, space):
if len(args) == 1:
l = get_arg(args, 0)
if type(l) == float:
if to_uint32(l) == l:
return space.NewArray(l)
else:
raise MakeError(
'RangeError',
'Invalid length specified for Array constructor (must be uint32)'
)
else:
return space.ConstructArray([l])
else:
return space.ConstructArray(list(args))
def isArray(this, args):
x = get_arg(args, 0)
return is_object(x) and x.Class == u'Array'
@@ -0,0 +1,14 @@
from ..conversions import *
from ..func_utils import *
def Boolean(this, args):
return to_boolean(get_arg(args, 0))
def BooleanConstructor(args, space):
temp = space.NewObject()
temp.prototype = space.BooleanPrototype
temp.Class = 'Boolean'
temp.value = to_boolean(get_arg(args, 0))
return temp
@@ -0,0 +1,11 @@
from __future__ import unicode_literals
from js2py.internals.conversions import *
from js2py.internals.func_utils import *
class ConsoleMethods:
def log(this, args):
x = ' '.join(to_string(e) for e in args)
print(x)
return undefined
@@ -0,0 +1,405 @@
from ..base import *
from .time_helpers import *
TZ_OFFSET = (time.altzone // 3600)
ABS_OFFSET = abs(TZ_OFFSET)
TZ_NAME = time.tzname[1]
ISO_FORMAT = '%s-%s-%sT%s:%s:%s.%sZ'
@Js
def Date(year, month, date, hours, minutes, seconds, ms):
return now().to_string()
Date.Class = 'Date'
def now():
return PyJsDate(int(time.time() * 1000), prototype=DatePrototype)
@Js
def UTC(year, month, date, hours, minutes, seconds, ms): # todo complete this
args = arguments
y = args[0].to_number()
m = args[1].to_number()
l = len(args)
dt = args[2].to_number() if l > 2 else Js(1)
h = args[3].to_number() if l > 3 else Js(0)
mi = args[4].to_number() if l > 4 else Js(0)
sec = args[5].to_number() if l > 5 else Js(0)
mili = args[6].to_number() if l > 6 else Js(0)
if not y.is_nan() and 0 <= y.value <= 99:
y = y + Js(1900)
t = TimeClip(MakeDate(MakeDay(y, m, dt), MakeTime(h, mi, sec, mili)))
return PyJsDate(t, prototype=DatePrototype)
@Js
def parse(string):
return PyJsDate(
TimeClip(parse_date(string.to_string().value)),
prototype=DatePrototype)
Date.define_own_property('now', {
'value': Js(now),
'enumerable': False,
'writable': True,
'configurable': True
})
Date.define_own_property('parse', {
'value': parse,
'enumerable': False,
'writable': True,
'configurable': True
})
Date.define_own_property('UTC', {
'value': UTC,
'enumerable': False,
'writable': True,
'configurable': True
})
class PyJsDate(PyJs):
Class = 'Date'
extensible = True
def __init__(self, value, prototype=None):
self.value = value
self.own = {}
self.prototype = prototype
# todo fix this problematic datetime part
def to_local_dt(self):
return datetime.datetime.utcfromtimestamp(
UTCToLocal(self.value) // 1000)
def to_utc_dt(self):
return datetime.datetime.utcfromtimestamp(self.value // 1000)
def local_strftime(self, pattern):
if self.value is NaN:
return 'Invalid Date'
try:
dt = self.to_local_dt()
except:
raise MakeError(
'TypeError',
'unsupported date range. Will fix in future versions')
try:
return dt.strftime(pattern)
except:
raise MakeError(
'TypeError',
'Could not generate date string from this date (limitations of python.datetime)'
)
def utc_strftime(self, pattern):
if self.value is NaN:
return 'Invalid Date'
try:
dt = self.to_utc_dt()
except:
raise MakeError(
'TypeError',
'unsupported date range. Will fix in future versions')
try:
return dt.strftime(pattern)
except:
raise MakeError(
'TypeError',
'Could not generate date string from this date (limitations of python.datetime)'
)
def parse_date(py_string): # todo support all date string formats
try:
try:
dt = datetime.datetime.strptime(py_string, "%Y-%m-%dT%H:%M:%S.%fZ")
except:
dt = datetime.datetime.strptime(py_string, "%Y-%m-%dT%H:%M:%SZ")
return MakeDate(
MakeDay(Js(dt.year), Js(dt.month - 1), Js(dt.day)),
MakeTime(
Js(dt.hour), Js(dt.minute), Js(dt.second),
Js(dt.microsecond // 1000)))
except:
raise MakeError(
'TypeError',
'Could not parse date %s - unsupported date format. Currently only supported format is RFC3339 utc. Sorry!'
% py_string)
def date_constructor(*args):
if len(args) >= 2:
return date_constructor2(*args)
elif len(args) == 1:
return date_constructor1(args[0])
else:
return date_constructor0()
def date_constructor0():
return now()
def date_constructor1(value):
v = value.to_primitive()
if v._type() == 'String':
v = parse_date(v.value)
else:
v = v.to_int()
return PyJsDate(TimeClip(v), prototype=DatePrototype)
def date_constructor2(*args):
y = args[0].to_number()
m = args[1].to_number()
l = len(args)
dt = args[2].to_number() if l > 2 else Js(1)
h = args[3].to_number() if l > 3 else Js(0)
mi = args[4].to_number() if l > 4 else Js(0)
sec = args[5].to_number() if l > 5 else Js(0)
mili = args[6].to_number() if l > 6 else Js(0)
if not y.is_nan() and 0 <= y.value <= 99:
y = y + Js(1900)
t = TimeClip(
LocalToUTC(MakeDate(MakeDay(y, m, dt), MakeTime(h, mi, sec, mili))))
return PyJsDate(t, prototype=DatePrototype)
Date.create = date_constructor
DatePrototype = PyJsDate(float('nan'), prototype=ObjectPrototype)
def check_date(obj):
if obj.Class != 'Date':
raise MakeError('TypeError', 'this is not a Date object')
class DateProto:
def toString():
check_date(this)
if this.value is NaN:
return 'Invalid Date'
offset = (UTCToLocal(this.value) - this.value) // msPerHour
return this.local_strftime(
'%a %b %d %Y %H:%M:%S GMT') + '%s00 (%s)' % (pad(
offset, 2, True), GetTimeZoneName(this.value))
def toDateString():
check_date(this)
return this.local_strftime('%d %B %Y')
def toTimeString():
check_date(this)
return this.local_strftime('%H:%M:%S')
def toLocaleString():
check_date(this)
return this.local_strftime('%d %B %Y %H:%M:%S')
def toLocaleDateString():
check_date(this)
return this.local_strftime('%d %B %Y')
def toLocaleTimeString():
check_date(this)
return this.local_strftime('%H:%M:%S')
def valueOf():
check_date(this)
return this.value
def getTime():
check_date(this)
return this.value
def getFullYear():
check_date(this)
if this.value is NaN:
return NaN
return YearFromTime(UTCToLocal(this.value))
def getUTCFullYear():
check_date(this)
if this.value is NaN:
return NaN
return YearFromTime(this.value)
def getMonth():
check_date(this)
if this.value is NaN:
return NaN
return MonthFromTime(UTCToLocal(this.value))
def getDate():
check_date(this)
if this.value is NaN:
return NaN
return DateFromTime(UTCToLocal(this.value))
def getUTCMonth():
check_date(this)
if this.value is NaN:
return NaN
return MonthFromTime(this.value)
def getUTCDate():
check_date(this)
if this.value is NaN:
return NaN
return DateFromTime(this.value)
def getDay():
check_date(this)
if this.value is NaN:
return NaN
return WeekDay(UTCToLocal(this.value))
def getUTCDay():
check_date(this)
if this.value is NaN:
return NaN
return WeekDay(this.value)
def getHours():
check_date(this)
if this.value is NaN:
return NaN
return HourFromTime(UTCToLocal(this.value))
def getUTCHours():
check_date(this)
if this.value is NaN:
return NaN
return HourFromTime(this.value)
def getMinutes():
check_date(this)
if this.value is NaN:
return NaN
return MinFromTime(UTCToLocal(this.value))
def getUTCMinutes():
check_date(this)
if this.value is NaN:
return NaN
return MinFromTime(this.value)
def getSeconds():
check_date(this)
if this.value is NaN:
return NaN
return SecFromTime(UTCToLocal(this.value))
def getUTCSeconds():
check_date(this)
if this.value is NaN:
return NaN
return SecFromTime(this.value)
def getMilliseconds():
check_date(this)
if this.value is NaN:
return NaN
return msFromTime(UTCToLocal(this.value))
def getUTCMilliseconds():
check_date(this)
if this.value is NaN:
return NaN
return msFromTime(this.value)
def getTimezoneOffset():
check_date(this)
if this.value is NaN:
return NaN
return (this.value - UTCToLocal(this.value)) // 60000
def setTime(time):
check_date(this)
this.value = TimeClip(time.to_number().to_int())
return this.value
def setMilliseconds(ms):
check_date(this)
t = UTCToLocal(this.value)
tim = MakeTime(
HourFromTime(t), MinFromTime(t), SecFromTime(t), ms.to_int())
u = TimeClip(LocalToUTC(MakeDate(Day(t), tim)))
this.value = u
return u
def setUTCMilliseconds(ms):
check_date(this)
t = this.value
tim = MakeTime(
HourFromTime(t), MinFromTime(t), SecFromTime(t), ms.to_int())
u = TimeClip(MakeDate(Day(t), tim))
this.value = u
return u
# todo Complete all setters!
def toUTCString():
check_date(this)
return this.utc_strftime('%d %B %Y %H:%M:%S')
def toISOString():
check_date(this)
t = this.value
year = YearFromTime(t)
month, day, hour, minute, second, milli = pad(
MonthFromTime(t) + 1), pad(DateFromTime(t)), pad(
HourFromTime(t)), pad(MinFromTime(t)), pad(
SecFromTime(t)), pad(msFromTime(t))
return ISO_FORMAT % (unicode(year) if 0 <= year <= 9999 else pad(
year, 6, True), month, day, hour, minute, second, milli)
def toJSON(key):
o = this.to_object()
tv = o.to_primitive('Number')
if tv.Class == 'Number' and not tv.is_finite():
return this.null
toISO = o.get('toISOString')
if not toISO.is_callable():
raise this.MakeError('TypeError', 'toISOString is not callable')
return toISO.call(o, ())
def pad(num, n=2, sign=False):
'''returns n digit string representation of the num'''
s = unicode(abs(num))
if len(s) < n:
s = '0' * (n - len(s)) + s
if not sign:
return s
if num >= 0:
return '+' + s
else:
return '-' + s
fill_prototype(DatePrototype, DateProto, default_attrs)
Date.define_own_property(
'prototype', {
'value': DatePrototype,
'enumerable': False,
'writable': False,
'configurable': False
})
DatePrototype.define_own_property('constructor', {
'value': Date,
'enumerable': False,
'writable': True,
'configurable': True
})
@@ -0,0 +1,76 @@
from ..base import *
from ..conversions import *
from ..func_utils import *
from pyjsparser import parse
from ..byte_trans import ByteCodeGenerator, Code
def Function(this, args):
# convert arguments to python list of strings
a = map(to_string, tuple(args))
_body = u';'
_args = ()
if len(a):
_body = u'%s;' % a[-1]
_args = a[:-1]
return executable_function(_body, _args, args.space, global_context=True)
def executable_function(_body, _args, space, global_context=True):
func_str = u'(function (%s) { ; %s ; });' % (u', '.join(_args), _body)
co = executable_code(
code_str=func_str, space=space, global_context=global_context)
return co()
# you can use this one lovely piece of function to compile and execute code on the fly! Watch out though as it may generate lots of code.
# todo tape cleanup? we dont know which pieces are needed and which are not so rather impossible without smarter machinery something like GC,
# a one solution would be to have a separate tape for functions
def executable_code(code_str, space, global_context=True):
# parse first to check if any SyntaxErrors
parsed = parse(code_str)
old_tape_len = len(space.byte_generator.exe.tape)
space.byte_generator.record_state()
start = space.byte_generator.exe.get_new_label()
skip = space.byte_generator.exe.get_new_label()
space.byte_generator.emit('JUMP', skip)
space.byte_generator.emit('LABEL', start)
space.byte_generator.emit(parsed)
space.byte_generator.emit('NOP')
space.byte_generator.emit('LABEL', skip)
space.byte_generator.emit('NOP')
space.byte_generator.restore_state()
space.byte_generator.exe.compile(
start_loc=old_tape_len
) # dont read the code from the beginning, dont be stupid!
ctx = space.GlobalObj if global_context else space.exe.current_ctx
def ex_code():
ret, status, token = space.byte_generator.exe.execute_fragment_under_context(
ctx, start, skip)
# todo Clean up the tape!
# this is NOT a way to do that because the fragment may contain the executable code! We dont want to remove it
#del space.byte_generator.exe.tape[old_tape_len:]
if status == 0:
return ret
elif status == 3:
raise token
else:
raise RuntimeError(
'Unexpected return status during JIT execution: %d' % status)
return ex_code
def _eval(this, args):
code_str = to_string(get_arg(args, 0))
return executable_code(code_str, args.space, global_context=True)()
def log(this, args):
print(' '.join(map(to_string, args)))
return undefined
@@ -0,0 +1,157 @@
from __future__ import unicode_literals
from ..conversions import *
from ..func_utils import *
import math
import random
CONSTANTS = {
'E': 2.7182818284590452354,
'LN10': 2.302585092994046,
'LN2': 0.6931471805599453,
'LOG2E': 1.4426950408889634,
'LOG10E': 0.4342944819032518,
'PI': 3.1415926535897932,
'SQRT1_2': 0.7071067811865476,
'SQRT2': 1.4142135623730951
}
class MathFunctions:
def abs(this, args):
x = get_arg(args, 0)
a = to_number(x)
if a != a: # it must be a nan
return NaN
return abs(a)
def acos(this, args):
x = get_arg(args, 0)
a = to_number(x)
if a != a: # it must be a nan
return NaN
try:
return math.acos(a)
except:
return NaN
def asin(this, args):
x = get_arg(args, 0)
a = to_number(x)
if a != a: # it must be a nan
return NaN
try:
return math.asin(a)
except:
return NaN
def atan(this, args):
x = get_arg(args, 0)
a = to_number(x)
if a != a: # it must be a nan
return NaN
return math.atan(a)
def atan2(this, args):
x = get_arg(args, 0)
y = get_arg(args, 1)
a = to_number(x)
b = to_number(y)
if a != a or b != b: # it must be a nan
return NaN
return math.atan2(a, b)
def ceil(this, args):
x = get_arg(args, 0)
a = to_number(x)
if a != a: # it must be a nan
return NaN
return float(math.ceil(a))
def floor(this, args):
x = get_arg(args, 0)
a = to_number(x)
if a != a: # it must be a nan
return NaN
return float(math.floor(a))
def round(this, args):
x = get_arg(args, 0)
a = to_number(x)
if a != a: # it must be a nan
return NaN
return float(round(a))
def sin(this, args):
x = get_arg(args, 0)
a = to_number(x)
if not is_finite(a): # it must be a nan
return NaN
return math.sin(a)
def cos(this, args):
x = get_arg(args, 0)
a = to_number(x)
if not is_finite(a): # it must be a nan
return NaN
return math.cos(a)
def tan(this, args):
x = get_arg(args, 0)
a = to_number(x)
if not is_finite(a): # it must be a nan
return NaN
return math.tan(a)
def log(this, args):
x = get_arg(args, 0)
a = to_number(x)
if a != a: # it must be a nan
return NaN
try:
return math.log(a)
except:
return NaN
def exp(this, args):
x = get_arg(args, 0)
a = to_number(x)
if a != a: # it must be a nan
return NaN
return math.exp(a)
def pow(this, args):
x = get_arg(args, 0)
y = get_arg(args, 1)
a = to_number(x)
b = to_number(y)
if a != a or b != b: # it must be a nan
return NaN
try:
return a**b
except:
return NaN
def sqrt(this, args):
x = get_arg(args, 0)
a = to_number(x)
if a != a: # it must be a nan
return NaN
try:
return a**0.5
except:
return NaN
def min(this, args):
if len(args) == 0:
return Infinity
return min(map(to_number, tuple(args)))
def max(this, args):
if len(args) == 0:
return -Infinity
return max(map(to_number, tuple(args)))
def random(this, args):
return random.random()
@@ -0,0 +1,27 @@
from __future__ import unicode_literals
from ..conversions import *
from ..func_utils import *
def Number(this, args):
if len(args) == 0:
return 0.
return to_number(args[0])
def NumberConstructor(args, space):
temp = space.NewObject()
temp.prototype = space.NumberPrototype
temp.Class = 'Number'
temp.value = float(to_number(get_arg(args, 0)) if len(args) > 0 else 0.)
return temp
CONSTS = {
'MAX_VALUE': 1.7976931348623157e308,
'MIN_VALUE': 5.0e-324,
'NaN': NaN,
'NEGATIVE_INFINITY': Infinity,
'POSITIVE_INFINITY': -Infinity
}
@@ -0,0 +1,204 @@
from __future__ import unicode_literals
from ..conversions import *
from ..func_utils import *
from ..base import is_data_descriptor
import six
def Object(this, args):
val = get_arg(args, 0)
if is_null(val) or is_undefined(val):
return args.space.NewObject()
return to_object(val, args.space)
def ObjectCreate(args, space):
if len(args):
val = get_arg(args, 0)
if is_object(val):
# Implementation dependent, but my will simply return :)
return val
elif type(val) in (NUMBER_TYPE, STRING_TYPE, BOOLEAN_TYPE):
return to_object(val, space)
return space.NewObject()
class ObjectMethods:
def getPrototypeOf(this, args):
obj = get_arg(args, 0)
if not is_object(obj):
raise MakeError('TypeError',
'Object.getPrototypeOf called on non-object')
return null if obj.prototype is None else obj.prototype
def getOwnPropertyDescriptor(this, args):
obj = get_arg(args, 0)
prop = get_arg(args, 1)
if not is_object(obj):
raise MakeError(
'TypeError',
'Object.getOwnPropertyDescriptor called on non-object')
desc = obj.own.get(to_string(prop))
return convert_to_js_type(desc, args.space)
def getOwnPropertyNames(this, args):
obj = get_arg(args, 0)
if not is_object(obj):
raise MakeError(
'TypeError',
'Object.getOwnPropertyDescriptor called on non-object')
return args.space.ConstructArray(obj.own.keys())
def create(this, args):
obj = get_arg(args, 0)
if not (is_object(obj) or is_null(obj)):
raise MakeError('TypeError',
'Object prototype may only be an Object or null')
temp = args.space.NewObject()
temp.prototype = None if is_null(obj) else obj
if len(args) > 1 and not is_undefined(args[1]):
if six.PY2:
args.tup = (args[1], )
ObjectMethods.defineProperties.__func__(temp, args)
else:
args.tup = (args[1], )
ObjectMethods.defineProperties(temp, args)
return temp
def defineProperty(this, args):
obj = get_arg(args, 0)
prop = get_arg(args, 1)
attrs = get_arg(args, 2)
if not is_object(obj):
raise MakeError('TypeError',
'Object.defineProperty called on non-object')
name = to_string(prop)
if not obj.define_own_property(name, ToPropertyDescriptor(attrs),
False):
raise MakeError('TypeError', 'Cannot redefine property: %s' % name)
return obj
def defineProperties(this, args):
obj = get_arg(args, 0)
properties = get_arg(args, 1)
if not is_object(obj):
raise MakeError('TypeError',
'Object.defineProperties called on non-object')
props = to_object(properties, args.space)
for k, v in props.own.items():
if not v.get('enumerable'):
continue
desc = ToPropertyDescriptor(props.get(unicode(k)))
if not obj.define_own_property(unicode(k), desc, False):
raise MakeError('TypeError',
'Failed to define own property: %s' % k)
return obj
def seal(this, args):
obj = get_arg(args, 0)
if not is_object(obj):
raise MakeError('TypeError', 'Object.seal called on non-object')
for desc in obj.own.values():
desc['configurable'] = False
obj.extensible = False
return obj
def freeze(this, args):
obj = get_arg(args, 0)
if not is_object(obj):
raise MakeError('TypeError', 'Object.freeze called on non-object')
for desc in obj.own.values():
desc['configurable'] = False
if is_data_descriptor(desc):
desc['writable'] = False
obj.extensible = False
return obj
def preventExtensions(this, args):
obj = get_arg(args, 0)
if not is_object(obj):
raise MakeError('TypeError',
'Object.preventExtensions on non-object')
obj.extensible = False
return obj
def isSealed(this, args):
obj = get_arg(args, 0)
if not is_object(obj):
raise MakeError('TypeError',
'Object.isSealed called on non-object')
if obj.extensible:
return False
for desc in obj.own.values():
if desc.get('configurable'):
return False
return True
def isFrozen(this, args):
obj = get_arg(args, 0)
if not is_object(obj):
raise MakeError('TypeError',
'Object.isFrozen called on non-object')
if obj.extensible:
return False
for desc in obj.own.values():
if desc.get('configurable'):
return False
if is_data_descriptor(desc) and desc.get('writable'):
return False
return True
def isExtensible(this, args):
obj = get_arg(args, 0)
if not is_object(obj):
raise MakeError('TypeError',
'Object.isExtensible called on non-object')
return obj.extensible
def keys(this, args):
obj = get_arg(args, 0)
if not is_object(obj):
raise MakeError('TypeError', 'Object.keys called on non-object')
return args.space.ConstructArray([
unicode(e) for e, d in six.iteritems(obj.own)
if d.get('enumerable')
])
# some utility functions:
def ToPropertyDescriptor(obj): # page 38 (50 absolute)
if not is_object(obj):
raise MakeError('TypeError',
'Can\'t convert non-object to property descriptor')
desc = {}
if obj.has_property('enumerable'):
desc['enumerable'] = to_boolean(obj.get('enumerable'))
if obj.has_property('configurable'):
desc['configurable'] = to_boolean(obj.get('configurable'))
if obj.has_property('value'):
desc['value'] = obj.get('value')
if obj.has_property('writable'):
desc['writable'] = to_boolean(obj.get('writable'))
if obj.has_property('get'):
cand = obj.get('get')
if not (is_undefined(cand) or is_callable(cand)):
raise MakeError(
'TypeError',
'Invalid getter (it has to be a function or undefined)')
desc['get'] = cand
if obj.has_property('set'):
cand = obj.get('set')
if not (is_undefined(cand) or is_callable(cand)):
raise MakeError(
'TypeError',
'Invalid setter (it has to be a function or undefined)')
desc['set'] = cand
if ('get' in desc or 'set' in desc) and ('value' in desc
or 'writable' in desc):
raise MakeError(
'TypeError',
'Invalid property. A property cannot both have accessors and be writable or have a value.'
)
return desc
@@ -0,0 +1,41 @@
from __future__ import unicode_literals
from ..conversions import *
from ..func_utils import *
from ..base import SpaceTuple
REG_EXP_FLAGS = ('g', 'i', 'm')
def RegExp(this, args):
pattern = get_arg(args, 0)
flags = get_arg(args, 1)
if GetClass(pattern) == 'RegExp':
if not is_undefined(flags):
raise MakeError(
'TypeError',
'Cannot supply flags when constructing one RegExp from another'
)
# return unchanged
return pattern
#pattern is not a regexp
if is_undefined(pattern):
pattern = u''
else:
pattern = to_string(pattern)
flags = to_string(flags) if not is_undefined(flags) else u''
for flag in flags:
if flag not in REG_EXP_FLAGS:
raise MakeError(
'SyntaxError',
'Invalid flags supplied to RegExp constructor "%s"' % flag)
if len(set(flags)) != len(flags):
raise MakeError(
'SyntaxError',
'Invalid flags supplied to RegExp constructor "%s"' % flags)
return args.space.NewRegExp(pattern, flags)
def RegExpCreate(args, space):
_args = SpaceTuple(args)
_args.space = space
return RegExp(undefined, _args)
@@ -0,0 +1,23 @@
from ..conversions import *
from ..func_utils import *
def fromCharCode(this, args):
res = u''
for e in args:
res += unichr(to_uint16(e))
return res
def String(this, args):
if len(args) == 0:
return u''
return to_string(args[0])
def StringConstructor(args, space):
temp = space.NewObject()
temp.prototype = space.StringPrototype
temp.Class = 'String'
temp.value = to_string(get_arg(args, 0)) if len(args) > 0 else u''
return temp
@@ -0,0 +1,209 @@
from __future__ import unicode_literals
# NOTE: t must be INT!!!
import time
import datetime
import warnings
try:
from tzlocal import get_localzone
LOCAL_ZONE = get_localzone()
except: # except all problems...
warnings.warn(
'Please install or fix tzlocal library (pip install tzlocal) in order to make Date object work better. Otherwise I will assume DST is in effect all the time'
)
class LOCAL_ZONE:
@staticmethod
def dst(*args):
return 1
from js2py.base import MakeError
CUM = (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365)
msPerDay = 86400000
msPerYear = int(86400000 * 365.242)
msPerSecond = 1000
msPerMinute = 60000
msPerHour = 3600000
HoursPerDay = 24
MinutesPerHour = 60
SecondsPerMinute = 60
NaN = float('nan')
LocalTZA = -time.timezone * msPerSecond
def DaylightSavingTA(t):
if t is NaN:
return t
try:
return int(
LOCAL_ZONE.dst(datetime.datetime.utcfromtimestamp(
t // 1000)).seconds) * 1000
except:
warnings.warn(
'Invalid datetime date, assumed DST time, may be inaccurate...',
Warning)
return 1
#raise MakeError('TypeError', 'date not supported by python.datetime. I will solve it in future versions')
def GetTimeZoneName(t):
return time.tzname[DaylightSavingTA(t) > 0]
def LocalToUTC(t):
return t - LocalTZA - DaylightSavingTA(t - LocalTZA)
def UTCToLocal(t):
return t + LocalTZA + DaylightSavingTA(t)
def Day(t):
return t // 86400000
def TimeWithinDay(t):
return t % 86400000
def DaysInYear(y):
if y % 4:
return 365
elif y % 100:
return 366
elif y % 400:
return 365
else:
return 366
def DayFromYear(y):
return 365 * (y - 1970) + (y - 1969) // 4 - (y - 1901) // 100 + (
y - 1601) // 400
def TimeFromYear(y):
return 86400000 * DayFromYear(y)
def YearFromTime(t):
guess = 1970 - t // 31556908800 # msPerYear
gt = TimeFromYear(guess)
if gt <= t:
while gt <= t:
guess += 1
gt = TimeFromYear(guess)
return guess - 1
else:
while gt > t:
guess -= 1
gt = TimeFromYear(guess)
return guess
def DayWithinYear(t):
return Day(t) - DayFromYear(YearFromTime(t))
def InLeapYear(t):
y = YearFromTime(t)
if y % 4:
return 0
elif y % 100:
return 1
elif y % 400:
return 0
else:
return 1
def MonthFromTime(t):
day = DayWithinYear(t)
leap = InLeapYear(t)
if day < 31:
return 0
day -= leap
if day < 59:
return 1
elif day < 90:
return 2
elif day < 120:
return 3
elif day < 151:
return 4
elif day < 181:
return 5
elif day < 212:
return 6
elif day < 243:
return 7
elif day < 273:
return 8
elif day < 304:
return 9
elif day < 334:
return 10
else:
return 11
def DateFromTime(t):
mon = MonthFromTime(t)
day = DayWithinYear(t)
return day - CUM[mon] - (1 if InLeapYear(t) and mon >= 2 else 0) + 1
def WeekDay(t):
# 0 == sunday
return (Day(t) + 4) % 7
def msFromTime(t):
return t % 1000
def SecFromTime(t):
return (t // 1000) % 60
def MinFromTime(t):
return (t // 60000) % 60
def HourFromTime(t):
return (t // 3600000) % 24
def MakeTime(hour, Min, sec, ms):
# takes PyJs objects and returns t
if not (hour.is_finite() and Min.is_finite() and sec.is_finite()
and ms.is_finite()):
return NaN
h, m, s, milli = hour.to_int(), Min.to_int(), sec.to_int(), ms.to_int()
return h * 3600000 + m * 60000 + s * 1000 + milli
def MakeDay(year, month, date):
# takes PyJs objects and returns t
if not (year.is_finite() and month.is_finite() and date.is_finite()):
return NaN
y, m, dt = year.to_int(), month.to_int(), date.to_int()
y += m // 12
mn = m % 12
d = DayFromYear(y) + CUM[mn] + dt - 1 + (1 if DaysInYear(y) == 366
and mn >= 2 else 0)
return d # ms per day
def MakeDate(day, time):
return 86400000 * day + time
def TimeClip(t):
if t != t or abs(t) == float('inf'):
return NaN
if abs(t) > 8.64 * 10**15:
return NaN
return int(t)
@@ -0,0 +1,141 @@
from __future__ import unicode_literals
# Type Conversions. to_type. All must return PyJs subclass instance
from .simplex import *
def to_primitive(self, hint=None):
if is_primitive(self):
return self
if hint is None and (self.Class == 'Number' or self.Class == 'Boolean'):
# favour number for Class== Number or Boolean default = String
hint = 'Number'
return self.default_value(hint)
def to_boolean(self):
typ = Type(self)
if typ == 'Boolean': # no need to convert
return self
elif typ == 'Null' or typ == 'Undefined': # they are both always false
return False
elif typ == 'Number': # false only for 0, and NaN
return self and self == self # test for nan (nan -> flase)
elif typ == 'String':
return bool(self)
else: # object - always True
return True
def to_number(self):
typ = Type(self)
if typ == 'Number': # or self.Class=='Number': # no need to convert
return self
elif typ == 'Null': # null is 0
return 0.
elif typ == 'Undefined': # undefined is NaN
return NaN
elif typ == 'Boolean': # 1 for True 0 for false
return float(self)
elif typ == 'String':
s = self.strip() # Strip white space
if not s: # '' is simply 0
return 0.
if 'x' in s or 'X' in s[:3]: # hex (positive only)
try: # try to convert
num = int(s, 16)
except ValueError: # could not convert -> NaN
return NaN
return float(num)
sign = 1 # get sign
if s[0] in '+-':
if s[0] == '-':
sign = -1
s = s[1:]
if s == 'Infinity': # Check for infinity keyword. 'NaN' will be NaN anyway.
return sign * Infinity
try: # decimal try
num = sign * float(s) # Converted
except ValueError:
return NaN # could not convert to decimal > return NaN
return float(num)
else: # object - most likely it will be NaN.
return to_number(to_primitive(self, 'Number'))
def to_string(self):
typ = Type(self)
if typ == 'String':
return self
elif typ == 'Null':
return 'null'
elif typ == 'Undefined':
return 'undefined'
elif typ == 'Boolean':
return 'true' if self else 'false'
elif typ == 'Number': # or self.Class=='Number':
return js_dtoa(self)
else: # object
return to_string(to_primitive(self, 'String'))
def to_object(self, space):
typ = Type(self)
if typ == 'Object':
return self
elif typ == 'Boolean': # Unsure ... todo check here
return space.Boolean.create((self, ), space)
elif typ == 'Number': # ?
return space.Number.create((self, ), space)
elif typ == 'String': # ?
return space.String.create((self, ), space)
elif typ == 'Null' or typ == 'Undefined':
raise MakeError('TypeError',
'undefined or null can\'t be converted to object')
else:
raise RuntimeError()
def to_int32(self):
num = to_number(self)
if is_nan(num) or is_infinity(num):
return 0
int32 = int(num) % 2**32
return int(int32 - 2**32 if int32 >= 2**31 else int32)
def to_int(self):
num = to_number(self)
if is_nan(num):
return 0
elif is_infinity(num):
return 10**20 if num > 0 else -10**20
return int(num)
def to_uint32(self):
num = to_number(self)
if is_nan(num) or is_infinity(num):
return 0
return int(num) % 2**32
def to_uint16(self):
num = to_number(self)
if is_nan(num) or is_infinity(num):
return 0
return int(num) % 2**16
def to_int16(self):
num = to_number(self)
if is_nan(num) or is_infinity(num):
return 0
int16 = int(num) % 2**16
return int(int16 - 2**16 if int16 >= 2**15 else int16)
def cok(self):
"""Check object coercible"""
if type(self) in (UNDEFINED_TYPE, NULL_TYPE):
raise MakeError('TypeError',
'undefined or null can\'t be converted to object')
@@ -0,0 +1,90 @@
# todo make sure what they mean by desc undefined? None or empty? Answer: None :) it can never be empty but None is sometimes returned.
# I am implementing everything as dicts to speed up property creation
# Warning: value, get, set props of dest are PyJs types. Rest is Py!
def is_data_descriptor(desc):
return desc and ('value' in desc or 'writable' in desc)
def is_accessor_descriptor(desc):
return desc and ('get' in desc or 'set' in desc)
def is_generic_descriptor(
desc
): # generic means not the data and not the setter - therefore it must be one that changes only enum and conf
return desc and not (is_data_descriptor(desc)
or is_accessor_descriptor(desc))
def from_property_descriptor(desc, space):
if not desc:
return {}
obj = space.NewObject()
if is_data_descriptor(desc):
obj.define_own_property(
'value', {
'value': desc['value'],
'writable': True,
'enumerable': True,
'configurable': True
}, False)
obj.define_own_property(
'writable', {
'value': desc['writable'],
'writable': True,
'enumerable': True,
'configurable': True
}, False)
else:
obj.define_own_property(
'get', {
'value': desc['get'],
'writable': True,
'enumerable': True,
'configurable': True
}, False)
obj.define_own_property(
'set', {
'value': desc['set'],
'writable': True,
'enumerable': True,
'configurable': True
}, False)
obj.define_own_property(
'writable', {
'value': desc['writable'],
'writable': True,
'enumerable': True,
'configurable': True
}, False)
obj.define_own_property(
'enumerable', {
'value': desc['enumerable'],
'writable': True,
'enumerable': True,
'configurable': True
}, False)
return obj
def to_property_descriptor(obj):
if obj._type() != 'Object':
raise TypeError()
desc = {}
for e in ('enumerable', 'configurable', 'writable'):
if obj.has_property(e):
desc[e] = obj.get(e).to_boolean().value
if obj.has_property('value'):
desc['value'] = obj.get('value')
for e in ('get', 'set'):
if obj.has_property(e):
cand = obj.get(e)
if not (cand.is_callable() or cand.is_undefined()):
raise TypeError()
if ('get' in desc or 'set' in desc) and ('value' in desc
or 'writable' in desc):
raise TypeError()
@@ -0,0 +1,281 @@
from __future__ import unicode_literals
from .base import Scope
from .func_utils import *
from .conversions import *
import six
from .prototypes.jsboolean import BooleanPrototype
from .prototypes.jserror import ErrorPrototype
from .prototypes.jsfunction import FunctionPrototype
from .prototypes.jsnumber import NumberPrototype
from .prototypes.jsobject import ObjectPrototype
from .prototypes.jsregexp import RegExpPrototype
from .prototypes.jsstring import StringPrototype
from .prototypes.jsarray import ArrayPrototype
from .prototypes import jsjson
from .prototypes import jsutils
from .constructors import jsnumber, jsstring, jsarray, jsboolean, jsregexp, jsmath, jsobject, jsfunction, jsconsole
def fill_proto(proto, proto_class, space):
for i in dir(proto_class):
e = getattr(proto_class, i)
if six.PY2:
if hasattr(e, '__func__'):
meth = e.__func__
else:
continue
else:
if hasattr(e, '__call__') and not i.startswith('__'):
meth = e
else:
continue
meth_name = meth.__name__.strip('_') # RexExp._exec -> RegExp.exec
js_meth = space.NewFunction(meth, space.ctx, (), meth_name, False, ())
set_non_enumerable(proto, meth_name, js_meth)
return proto
def easy_func(f, space):
return space.NewFunction(f, space.ctx, (), f.__name__, False, ())
def Empty(this, args):
return undefined
def set_non_enumerable(obj, name, prop):
obj.define_own_property(
unicode(name), {
'value': prop,
'writable': True,
'enumerable': False,
'configurable': True
}, True)
def set_protected(obj, name, prop):
obj.define_own_property(
unicode(name), {
'value': prop,
'writable': False,
'enumerable': False,
'configurable': False
}, True)
def fill_space(space, byte_generator):
# set global scope
global_scope = Scope({}, space, parent=None)
global_scope.THIS_BINDING = global_scope
global_scope.registers(byte_generator.declared_vars)
space.GlobalObj = global_scope
space.byte_generator = byte_generator
# first init all protos, later take care of constructors and details
# Function must be first obviously, we have to use a small trick to do that...
function_proto = space.NewFunction(Empty, space.ctx, (), 'Empty', False,
())
space.FunctionPrototype = function_proto # this will fill the prototypes of the methods!
fill_proto(function_proto, FunctionPrototype, space)
# Object next
object_proto = space.NewObject() # no proto
fill_proto(object_proto, ObjectPrototype, space)
space.ObjectPrototype = object_proto
function_proto.prototype = object_proto
# Number
number_proto = space.NewObject()
number_proto.prototype = object_proto
fill_proto(number_proto, NumberPrototype, space)
number_proto.value = 0.
number_proto.Class = 'Number'
space.NumberPrototype = number_proto
# String
string_proto = space.NewObject()
string_proto.prototype = object_proto
fill_proto(string_proto, StringPrototype, space)
string_proto.value = u''
string_proto.Class = 'String'
space.StringPrototype = string_proto
# Boolean
boolean_proto = space.NewObject()
boolean_proto.prototype = object_proto
fill_proto(boolean_proto, BooleanPrototype, space)
boolean_proto.value = False
boolean_proto.Class = 'Boolean'
space.BooleanPrototype = boolean_proto
# Array
array_proto = space.NewArray(0)
array_proto.prototype = object_proto
fill_proto(array_proto, ArrayPrototype, space)
space.ArrayPrototype = array_proto
# JSON
json = space.NewObject()
json.put(u'stringify', easy_func(jsjson.stringify, space))
json.put(u'parse', easy_func(jsjson.parse, space))
# Utils
parseFloat = easy_func(jsutils.parseFloat, space)
parseInt = easy_func(jsutils.parseInt, space)
isNaN = easy_func(jsutils.isNaN, space)
isFinite = easy_func(jsutils.isFinite, space)
# Error
error_proto = space.NewError(u'Error', u'')
error_proto.prototype = object_proto
error_proto.put(u'name', u'Error')
fill_proto(error_proto, ErrorPrototype, space)
space.ErrorPrototype = error_proto
def construct_constructor(typ):
def creator(this, args):
message = get_arg(args, 0)
if not is_undefined(message):
msg = to_string(message)
else:
msg = u''
return space.NewError(typ, msg)
j = easy_func(creator, space)
j.name = unicode(typ)
set_protected(j, 'prototype', space.ERROR_TYPES[typ])
set_non_enumerable(space.ERROR_TYPES[typ], 'constructor', j)
def new_create(args, space):
message = get_arg(args, 0)
if not is_undefined(message):
msg = to_string(message)
else:
msg = u''
return space.NewError(typ, msg)
j.create = new_create
return j
# fill remaining error types
error_constructors = {}
for err_type_name in (u'Error', u'EvalError', u'RangeError',
u'ReferenceError', u'SyntaxError', u'TypeError',
u'URIError'):
extra_err = space.NewError(u'Error', u'')
extra_err.put(u'name', err_type_name)
setattr(space, err_type_name + u'Prototype', extra_err)
error_constructors[err_type_name] = construct_constructor(
err_type_name)
assert space.TypeErrorPrototype is not None
# RegExp
regexp_proto = space.NewRegExp(u'(?:)', u'')
regexp_proto.prototype = object_proto
fill_proto(regexp_proto, RegExpPrototype, space)
space.RegExpPrototype = regexp_proto
# Json
# now all these boring constructors...
# Number
number = easy_func(jsnumber.Number, space)
space.Number = number
number.create = jsnumber.NumberConstructor
set_non_enumerable(number_proto, 'constructor', number)
set_protected(number, 'prototype', number_proto)
# number has some extra constants
for k, v in jsnumber.CONSTS.items():
set_protected(number, k, v)
# String
string = easy_func(jsstring.String, space)
space.String = string
string.create = jsstring.StringConstructor
set_non_enumerable(string_proto, 'constructor', string)
set_protected(string, 'prototype', string_proto)
# string has an extra function
set_non_enumerable(string, 'fromCharCode',
easy_func(jsstring.fromCharCode, space))
# Boolean
boolean = easy_func(jsboolean.Boolean, space)
space.Boolean = boolean
boolean.create = jsboolean.BooleanConstructor
set_non_enumerable(boolean_proto, 'constructor', boolean)
set_protected(boolean, 'prototype', boolean_proto)
# Array
array = easy_func(jsarray.Array, space)
space.Array = array
array.create = jsarray.ArrayConstructor
set_non_enumerable(array_proto, 'constructor', array)
set_protected(array, 'prototype', array_proto)
array.put(u'isArray', easy_func(jsarray.isArray, space))
# RegExp
regexp = easy_func(jsregexp.RegExp, space)
space.RegExp = regexp
regexp.create = jsregexp.RegExpCreate
set_non_enumerable(regexp_proto, 'constructor', regexp)
set_protected(regexp, 'prototype', regexp_proto)
# Object
_object = easy_func(jsobject.Object, space)
space.Object = _object
_object.create = jsobject.ObjectCreate
set_non_enumerable(object_proto, 'constructor', _object)
set_protected(_object, 'prototype', object_proto)
fill_proto(_object, jsobject.ObjectMethods, space)
# Function
function = easy_func(jsfunction.Function, space)
space.Function = function
# Math
math = space.NewObject()
math.Class = 'Math'
fill_proto(math, jsmath.MathFunctions, space)
for k, v in jsmath.CONSTANTS.items():
set_protected(math, k, v)
console = space.NewObject()
fill_proto(console, jsconsole.ConsoleMethods, space)
# set global object
builtins = {
'String': string,
'Number': number,
'Boolean': boolean,
'RegExp': regexp,
'exports': convert_to_js_type({}, space),
'Math': math,
#'Date',
'Object': _object,
'Function': function,
'JSON': json,
'Array': array,
'parseFloat': parseFloat,
'parseInt': parseInt,
'isFinite': isFinite,
'isNaN': isNaN,
'eval': easy_func(jsfunction._eval, space),
'console': console,
'log': console.get(u'log'),
}
builtins.update(error_constructors)
set_protected(global_scope, 'NaN', NaN)
set_protected(global_scope, 'Infinity', Infinity)
for k, v in builtins.items():
set_non_enumerable(global_scope, k, v)
@@ -0,0 +1,73 @@
from .simplex import *
from .conversions import *
import six
if six.PY3:
basestring = str
long = int
xrange = range
unicode = str
def get_arg(arguments, n):
if len(arguments) <= n:
return undefined
return arguments[n]
def ensure_js_types(args, space=None):
return tuple(convert_to_js_type(e, space=space) for e in args)
def convert_to_js_type(e, space=None):
t = type(e)
if is_js_type(e):
return e
if t in (int, long, float):
return float(e)
elif isinstance(t, basestring):
return unicode(t)
elif t in (list, tuple):
if space is None:
raise MakeError(
'TypeError',
'Actually an internal error, could not convert to js type because space not specified'
)
return space.ConstructArray(ensure_js_types(e, space=space))
elif t == dict:
if space is None:
raise MakeError(
'TypeError',
'Actually an internal error, could not convert to js type because space not specified'
)
new = {}
for k, v in e.items():
new[to_string(convert_to_js_type(k, space))] = convert_to_js_type(
v, space)
return space.ConstructObject(new)
else:
raise MakeError('TypeError', 'Could not convert to js type!')
def is_js_type(e):
if type(e) in PRIMITIVES:
return True
elif hasattr(e, 'Class') and hasattr(e, 'value'): # not perfect but works
return True
else:
return False
# todo optimise these 2!
def js_array_to_tuple(arr):
length = to_uint32(arr.get(u'length'))
return tuple(arr.get(unicode(e)) for e in xrange(length))
def js_array_to_list(arr):
length = to_uint32(arr.get(u'length'))
return [arr.get(unicode(e)) for e in xrange(length)]
def js_arr_length(arr):
return to_uint32(arr.get(u'length'))
@@ -0,0 +1,805 @@
from .operations import *
from .base import get_member, get_member_dot, PyJsFunction, Scope
class OP_CODE(object):
_params = []
# def eval(self, ctx):
# raise
def __repr__(self):
return self.__class__.__name__ + str(
tuple([getattr(self, e) for e in self._params]))
# --------------------- UNARY ----------------------
class UNARY_OP(OP_CODE):
_params = ['operator']
def __init__(self, operator):
self.operator = operator
def eval(self, ctx):
val = ctx.stack.pop()
ctx.stack.append(UNARY_OPERATIONS[self.operator](val))
# special unary operations
class TYPEOF(OP_CODE):
_params = ['identifier']
def __init__(self, identifier):
self.identifier = identifier
def eval(self, ctx):
# typeof something_undefined does not throw reference error
val = ctx.get(self.identifier,
False) # <= this makes it slightly different!
ctx.stack.append(typeof_uop(val))
class POSTFIX(OP_CODE):
_params = ['cb', 'ca', 'identifier']
def __init__(self, post, incr, identifier):
self.identifier = identifier
self.cb = 1 if incr else -1
self.ca = -self.cb if post else 0
def eval(self, ctx):
target = to_number(ctx.get(self.identifier)) + self.cb
ctx.put(self.identifier, target)
ctx.stack.append(target + self.ca)
class POSTFIX_MEMBER(OP_CODE):
_params = ['cb', 'ca']
def __init__(self, post, incr):
self.cb = 1 if incr else -1
self.ca = -self.cb if post else 0
def eval(self, ctx):
name = ctx.stack.pop()
left = ctx.stack.pop()
target = to_number(get_member(left, name, ctx.space)) + self.cb
if type(left) not in PRIMITIVES:
left.put_member(name, target)
ctx.stack.append(target + self.ca)
class POSTFIX_MEMBER_DOT(OP_CODE):
_params = ['cb', 'ca', 'prop']
def __init__(self, post, incr, prop):
self.cb = 1 if incr else -1
self.ca = -self.cb if post else 0
self.prop = prop
def eval(self, ctx):
left = ctx.stack.pop()
target = to_number(get_member_dot(left, self.prop,
ctx.space)) + self.cb
if type(left) not in PRIMITIVES:
left.put(self.prop, target)
ctx.stack.append(target + self.ca)
class DELETE(OP_CODE):
_params = ['name']
def __init__(self, name):
self.name = name
def eval(self, ctx):
ctx.stack.append(ctx.delete(self.name))
class DELETE_MEMBER(OP_CODE):
def eval(self, ctx):
prop = to_string(ctx.stack.pop())
obj = to_object(ctx.stack.pop(), ctx)
ctx.stack.append(obj.delete(prop, False))
# --------------------- BITWISE ----------------------
class BINARY_OP(OP_CODE):
_params = ['operator']
def __init__(self, operator):
self.operator = operator
def eval(self, ctx):
right = ctx.stack.pop()
left = ctx.stack.pop()
ctx.stack.append(BINARY_OPERATIONS[self.operator](left, right))
# &&, || and conditional are implemented in bytecode
# --------------------- JUMPS ----------------------
# simple label that will be removed from code after compilation. labels ID will be translated
# to source code position.
class LABEL(OP_CODE):
_params = ['num']
def __init__(self, num):
self.num = num
# I implemented interpreter in the way that when an integer is returned by eval operation the execution will jump
# to the location of the label (it is loc = label_locations[label])
class BASE_JUMP(OP_CODE):
_params = ['label']
def __init__(self, label):
self.label = label
class JUMP(BASE_JUMP):
def eval(self, ctx):
return self.label
class JUMP_IF_TRUE(BASE_JUMP):
def eval(self, ctx):
val = ctx.stack.pop()
if to_boolean(val):
return self.label
class JUMP_IF_EQ(BASE_JUMP):
# this one is used in switch statement - compares last 2 values using === operator and jumps popping both if true else pops last.
def eval(self, ctx):
cmp = ctx.stack.pop()
if strict_equality_op(ctx.stack[-1], cmp):
ctx.stack.pop()
return self.label
class JUMP_IF_TRUE_WITHOUT_POP(BASE_JUMP):
def eval(self, ctx):
val = ctx.stack[-1]
if to_boolean(val):
return self.label
class JUMP_IF_FALSE(BASE_JUMP):
def eval(self, ctx):
val = ctx.stack.pop()
if not to_boolean(val):
return self.label
class JUMP_IF_FALSE_WITHOUT_POP(BASE_JUMP):
def eval(self, ctx):
val = ctx.stack[-1]
if not to_boolean(val):
return self.label
class POP(OP_CODE):
def eval(self, ctx):
# todo remove this check later
assert len(ctx.stack), 'Popped from empty stack!'
del ctx.stack[-1]
# class REDUCE(OP_CODE):
# def eval(self, ctx):
# assert len(ctx.stack)==2
# ctx.stack[0] = ctx.stack[1]
# del ctx.stack[1]
# --------------- LOADING --------------
class LOAD_NONE(OP_CODE): # be careful with this :)
_params = []
def eval(self, ctx):
ctx.stack.append(None)
class LOAD_N_TUPLE(
OP_CODE
): # loads the tuple composed of n last elements on stack. elements are popped.
_params = ['n']
def __init__(self, n):
self.n = n
def eval(self, ctx):
tup = tuple(ctx.stack[-self.n:])
del ctx.stack[-self.n:]
ctx.stack.append(tup)
class LOAD_UNDEFINED(OP_CODE):
def eval(self, ctx):
ctx.stack.append(undefined)
class LOAD_NULL(OP_CODE):
def eval(self, ctx):
ctx.stack.append(null)
class LOAD_BOOLEAN(OP_CODE):
_params = ['val']
def __init__(self, val):
assert val in (0, 1)
self.val = bool(val)
def eval(self, ctx):
ctx.stack.append(self.val)
class LOAD_STRING(OP_CODE):
_params = ['val']
def __init__(self, val):
assert isinstance(val, basestring)
self.val = unicode(val)
def eval(self, ctx):
ctx.stack.append(self.val)
class LOAD_NUMBER(OP_CODE):
_params = ['val']
def __init__(self, val):
assert isinstance(val, (float, int, long))
self.val = float(val)
def eval(self, ctx):
ctx.stack.append(self.val)
class LOAD_REGEXP(OP_CODE):
_params = ['body', 'flags']
def __init__(self, body, flags):
self.body = body
self.flags = flags
def eval(self, ctx):
# we have to generate a new regexp - they are mutable
ctx.stack.append(ctx.space.NewRegExp(self.body, self.flags))
class LOAD_FUNCTION(OP_CODE):
_params = ['start', 'params', 'name', 'is_declaration', 'definitions']
def __init__(self, start, params, name, is_declaration, definitions):
assert type(start) == int
self.start = start # its an ID of label pointing to the beginning of the function bytecode
self.params = params
self.name = name
self.is_declaration = bool(is_declaration)
self.definitions = tuple(set(definitions + params))
def eval(self, ctx):
ctx.stack.append(
ctx.space.NewFunction(self.start, ctx, self.params, self.name,
self.is_declaration, self.definitions))
class LOAD_OBJECT(OP_CODE):
_params = [
'props'
] # props are py string pairs (prop_name, kind): kind can be either i, g or s. (init, get, set)
def __init__(self, props):
self.num = len(props)
self.props = props
def eval(self, ctx):
obj = ctx.space.NewObject()
if self.num:
obj._init(self.props, ctx.stack[-self.num:])
del ctx.stack[-self.num:]
ctx.stack.append(obj)
class LOAD_ARRAY(OP_CODE):
_params = ['num']
def __init__(self, num):
self.num = num
def eval(self, ctx):
arr = ctx.space.NewArray(self.num)
if self.num:
arr._init(ctx.stack[-self.num:])
del ctx.stack[-self.num:]
ctx.stack.append(arr)
class LOAD_THIS(OP_CODE):
def eval(self, ctx):
ctx.stack.append(ctx.THIS_BINDING)
class LOAD(OP_CODE): # todo check!
_params = ['identifier']
def __init__(self, identifier):
self.identifier = identifier
# 11.1.2
def eval(self, ctx):
ctx.stack.append(ctx.get(self.identifier, throw=True))
class LOAD_MEMBER(OP_CODE):
def eval(self, ctx):
prop = ctx.stack.pop()
obj = ctx.stack.pop()
ctx.stack.append(get_member(obj, prop, ctx.space))
class LOAD_MEMBER_DOT(OP_CODE):
_params = ['prop']
def __init__(self, prop):
self.prop = prop
def eval(self, ctx):
obj = ctx.stack.pop()
ctx.stack.append(get_member_dot(obj, self.prop, ctx.space))
# --------------- STORING --------------
class STORE(OP_CODE):
_params = ['identifier']
def __init__(self, identifier):
self.identifier = identifier
def eval(self, ctx):
value = ctx.stack[-1] # don't pop
ctx.put(self.identifier, value)
class STORE_MEMBER(OP_CODE):
def eval(self, ctx):
value = ctx.stack.pop()
name = ctx.stack.pop()
left = ctx.stack.pop()
typ = type(left)
if typ in PRIMITIVES:
prop = to_string(name)
if typ == NULL_TYPE:
raise MakeError('TypeError',
"Cannot set property '%s' of null" % prop)
elif typ == UNDEFINED_TYPE:
raise MakeError('TypeError',
"Cannot set property '%s' of undefined" % prop)
# just ignore...
else:
left.put_member(name, value)
ctx.stack.append(value)
class STORE_MEMBER_DOT(OP_CODE):
_params = ['prop']
def __init__(self, prop):
self.prop = prop
def eval(self, ctx):
value = ctx.stack.pop()
left = ctx.stack.pop()
typ = type(left)
if typ in PRIMITIVES:
if typ == NULL_TYPE:
raise MakeError('TypeError',
"Cannot set property '%s' of null" % self.prop)
elif typ == UNDEFINED_TYPE:
raise MakeError(
'TypeError',
"Cannot set property '%s' of undefined" % self.prop)
# just ignore...
else:
left.put(self.prop, value)
ctx.stack.append(value)
class STORE_OP(OP_CODE):
_params = ['identifier', 'op']
def __init__(self, identifier, op):
self.identifier = identifier
self.op = op
def eval(self, ctx):
value = ctx.stack.pop()
new_value = BINARY_OPERATIONS[self.op](ctx.get(self.identifier), value)
ctx.put(self.identifier, new_value)
ctx.stack.append(new_value)
class STORE_MEMBER_OP(OP_CODE):
_params = ['op']
def __init__(self, op):
self.op = op
def eval(self, ctx):
value = ctx.stack.pop()
name = ctx.stack.pop()
left = ctx.stack.pop()
typ = type(left)
if typ in PRIMITIVES:
if typ is NULL_TYPE:
raise MakeError(
'TypeError',
"Cannot set property '%s' of null" % to_string(name))
elif typ is UNDEFINED_TYPE:
raise MakeError(
'TypeError',
"Cannot set property '%s' of undefined" % to_string(name))
ctx.stack.append(BINARY_OPERATIONS[self.op](get_member(
left, name, ctx.space), value))
return
else:
ctx.stack.append(BINARY_OPERATIONS[self.op](get_member(
left, name, ctx.space), value))
left.put_member(name, ctx.stack[-1])
class STORE_MEMBER_DOT_OP(OP_CODE):
_params = ['prop', 'op']
def __init__(self, prop, op):
self.prop = prop
self.op = op
def eval(self, ctx):
value = ctx.stack.pop()
left = ctx.stack.pop()
typ = type(left)
if typ in PRIMITIVES:
if typ == NULL_TYPE:
raise MakeError('TypeError',
"Cannot set property '%s' of null" % self.prop)
elif typ == UNDEFINED_TYPE:
raise MakeError(
'TypeError',
"Cannot set property '%s' of undefined" % self.prop)
ctx.stack.append(BINARY_OPERATIONS[self.op](get_member_dot(
left, self.prop, ctx.space), value))
return
else:
ctx.stack.append(BINARY_OPERATIONS[self.op](get_member_dot(
left, self.prop, ctx.space), value))
left.put(self.prop, ctx.stack[-1])
# --------------- CALLS --------------
def bytecode_call(ctx, func, this, args):
if type(func) is not PyJsFunction:
raise MakeError('TypeError', "%s is not a function" % Type(func))
if func.is_native: # call to built-in function or method
ctx.stack.append(func.call(this, args))
return None
# therefore not native. we have to return (new_context, function_label) to instruct interpreter to call
return func._generate_my_context(this, args), func.code
class CALL(OP_CODE):
def eval(self, ctx):
args = ctx.stack.pop()
func = ctx.stack.pop()
return bytecode_call(ctx, func, ctx.space.GlobalObj, args)
class CALL_METHOD(OP_CODE):
def eval(self, ctx):
args = ctx.stack.pop()
prop = ctx.stack.pop()
base = ctx.stack.pop()
func = get_member(base, prop, ctx.space)
return bytecode_call(ctx, func, base, args)
class CALL_METHOD_DOT(OP_CODE):
_params = ['prop']
def __init__(self, prop):
self.prop = prop
def eval(self, ctx):
args = ctx.stack.pop()
base = ctx.stack.pop()
func = get_member_dot(base, self.prop, ctx.space)
return bytecode_call(ctx, func, base, args)
class CALL_NO_ARGS(OP_CODE):
def eval(self, ctx):
func = ctx.stack.pop()
return bytecode_call(ctx, func, ctx.space.GlobalObj, ())
class CALL_METHOD_NO_ARGS(OP_CODE):
def eval(self, ctx):
prop = ctx.stack.pop()
base = ctx.stack.pop()
func = get_member(base, prop, ctx.space)
return bytecode_call(ctx, func, base, ())
class CALL_METHOD_DOT_NO_ARGS(OP_CODE):
_params = ['prop']
def __init__(self, prop):
self.prop = prop
def eval(self, ctx):
base = ctx.stack.pop()
func = get_member_dot(base, self.prop, ctx.space)
return bytecode_call(ctx, func, base, ())
class NOP(OP_CODE):
def eval(self, ctx):
pass
class RETURN(OP_CODE):
def eval(
self, ctx
): # remember to load the return value on stack before using RETURN op.
return (None, None)
class NEW(OP_CODE):
def eval(self, ctx):
args = ctx.stack.pop()
constructor = ctx.stack.pop()
if type(constructor) in PRIMITIVES or not hasattr(
constructor, 'create'):
raise MakeError('TypeError',
'%s is not a constructor' % Type(constructor))
ctx.stack.append(constructor.create(args, space=ctx.space))
class NEW_NO_ARGS(OP_CODE):
def eval(self, ctx):
constructor = ctx.stack.pop()
if type(constructor) in PRIMITIVES or not hasattr(
constructor, 'create'):
raise MakeError('TypeError',
'%s is not a constructor' % Type(constructor))
ctx.stack.append(constructor.create((), space=ctx.space))
# --------------- EXCEPTIONS --------------
class THROW(OP_CODE):
def eval(self, ctx):
raise MakeError(None, None, ctx.stack.pop())
class TRY_CATCH_FINALLY(OP_CODE):
_params = [
'try_label', 'catch_label', 'catch_variable', 'finally_label',
'finally_present', 'end_label'
]
def __init__(self, try_label, catch_label, catch_variable, finally_label,
finally_present, end_label):
self.try_label = try_label
self.catch_label = catch_label
self.catch_variable = catch_variable
self.finally_label = finally_label
self.finally_present = finally_present
self.end_label = end_label
def eval(self, ctx):
# 4 different exectution results
# 0=normal, 1=return, 2=jump_outside, 3=errors
# execute_fragment_under_context returns:
# (return_value, typ, jump_loc/error)
ctx.stack.pop()
# execute try statement
try_status = ctx.space.exe.execute_fragment_under_context(
ctx, self.try_label, self.catch_label)
errors = try_status[1] == 3
# catch
if errors and self.catch_variable is not None:
# generate catch block context...
catch_context = Scope({
self.catch_variable:
try_status[2].get_thrown_value(ctx.space)
}, ctx.space, ctx)
catch_context.THIS_BINDING = ctx.THIS_BINDING
catch_status = ctx.space.exe.execute_fragment_under_context(
catch_context, self.catch_label, self.finally_label)
else:
catch_status = None
# finally
if self.finally_present:
finally_status = ctx.space.exe.execute_fragment_under_context(
ctx, self.finally_label, self.end_label)
else:
finally_status = None
# now return controls
other_status = catch_status or try_status
if finally_status is None or (finally_status[1] == 0
and other_status[1] != 0):
winning_status = other_status
else:
winning_status = finally_status
val, typ, spec = winning_status
if typ == 0: # normal
ctx.stack.append(val)
return
elif typ == 1: # return
ctx.stack.append(spec)
return None, None # send return signal
elif typ == 2: # jump outside
ctx.stack.append(val)
return spec
elif typ == 3:
# throw is made with empty stack as usual
raise spec
else:
raise RuntimeError('Invalid return code')
# ------------ WITH + ITERATORS ----------
class WITH(OP_CODE):
_params = ['beg_label', 'end_label']
def __init__(self, beg_label, end_label):
self.beg_label = beg_label
self.end_label = end_label
def eval(self, ctx):
obj = to_object(ctx.stack.pop(), ctx.space)
with_context = Scope(
obj, ctx.space, ctx) # todo actually use the obj to modify the ctx
with_context.THIS_BINDING = ctx.THIS_BINDING
status = ctx.space.exe.execute_fragment_under_context(
with_context, self.beg_label, self.end_label)
val, typ, spec = status
if typ != 3: # exception
ctx.stack.pop()
if typ == 0: # normal
ctx.stack.append(val)
return
elif typ == 1: # return
ctx.stack.append(spec)
return None, None # send return signal
elif typ == 2: # jump outside
ctx.stack.append(val)
return spec
elif typ == 3: # exception
# throw is made with empty stack as usual
raise spec
else:
raise RuntimeError('Invalid return code')
class FOR_IN(OP_CODE):
_params = ['name', 'body_start_label', 'continue_label', 'break_label']
def __init__(self, name, body_start_label, continue_label, break_label):
self.name = name
self.body_start_label = body_start_label
self.continue_label = continue_label
self.break_label = break_label
def eval(self, ctx):
iterable = ctx.stack.pop()
if is_null(iterable) or is_undefined(iterable):
ctx.stack.pop()
ctx.stack.append(undefined)
return self.break_label
obj = to_object(iterable, ctx.space)
for e in sorted(obj.own):
if not obj.own[e]['enumerable']:
continue
ctx.put(
self.name, e
) # JS would have been so much nicer if this was ctx.space.put(self.name, obj.get(e))
# evaluate the body
status = ctx.space.exe.execute_fragment_under_context(
ctx, self.body_start_label, self.break_label)
val, typ, spec = status
if typ != 3: # exception
ctx.stack.pop()
if typ == 0: # normal
ctx.stack.append(val)
continue
elif typ == 1: # return
ctx.stack.append(spec)
return None, None # send return signal
elif typ == 2: # jump outside
# now have to figure out whether this is a continue or something else...
ctx.stack.append(val)
if spec == self.continue_label:
# just a continue, perform next iteration as normal
continue
return spec # break or smth, go there and finish the iteration
elif typ == 3: # exception
# throw is made with empty stack as usual
raise spec
else:
raise RuntimeError('Invalid return code')
return self.break_label
# all opcodes...
OP_CODES = {}
g = ''
for g in globals():
try:
if not issubclass(globals()[g], OP_CODE) or g is 'OP_CODE':
continue
except:
continue
OP_CODES[g] = globals()[g]
@@ -0,0 +1,314 @@
from __future__ import unicode_literals
from .simplex import *
from .conversions import *
# ------------------------------------------------------------------------------
# Unary operations
# -x
def minus_uop(self):
return -to_number(self)
# +x
def plus_uop(self): # +u
return to_number(self)
# !x
def logical_negation_uop(self): # !u cant do 'not u' :(
return not to_boolean(self)
# typeof x
def typeof_uop(self):
if is_callable(self):
return u'function'
typ = Type(self).lower()
if typ == u'null':
typ = u'object' # absolutely idiotic...
return typ
# ~u
def bit_invert_uop(self):
return float(to_int32(float(~to_int32(self))))
# void
def void_op(self):
return undefined
UNARY_OPERATIONS = {
'+': plus_uop,
'-': minus_uop,
'!': logical_negation_uop,
'~': bit_invert_uop,
'void': void_op,
'typeof':
typeof_uop, # this one only for member expressions! for identifiers its slightly different...
}
# ------------------------------------------------------------------------------
# ----- binary ops -------
# Bitwise operators
# <<, >>, &, ^, |, ~
# <<
def bit_lshift_op(self, other):
lnum = to_int32(self)
rnum = to_uint32(other)
shiftCount = rnum & 0x1F
return float(to_int32(float(lnum << shiftCount)))
# >>
def bit_rshift_op(self, other):
lnum = to_int32(self)
rnum = to_uint32(other)
shiftCount = rnum & 0x1F
return float(to_int32(float(lnum >> shiftCount)))
# >>>
def bit_bshift_op(self, other):
lnum = to_uint32(self)
rnum = to_uint32(other)
shiftCount = rnum & 0x1F
return float(to_uint32(float(lnum >> shiftCount)))
# &
def bit_and_op(self, other):
lnum = to_int32(self)
rnum = to_int32(other)
return float(to_int32(float(lnum & rnum)))
# ^
def bit_xor_op(self, other):
lnum = to_int32(self)
rnum = to_int32(other)
return float(to_int32(float(lnum ^ rnum)))
# |
def bit_or_op(self, other):
lnum = to_int32(self)
rnum = to_int32(other)
return float(to_int32(float(lnum | rnum)))
# Additive operators
# + and - are implemented here
# +
def add_op(self, other):
if type(self) is float and type(other) is float:
return self + other
if type(self) is unicode and type(other) is unicode:
return self + other
# standard way...
a = to_primitive(self)
b = to_primitive(other)
if type(a) is unicode or type(b) is unicode: # string wins hehe
return to_string(a) + to_string(b)
return to_number(a) + to_number(b)
# -
def sub_op(self, other):
return to_number(self) - to_number(other)
# Multiplicative operators
# *, / and % are implemented here
# *
def mul_op(self, other):
return to_number(self) * to_number(other)
# /
def div_op(self, other):
a = to_number(self)
b = to_number(other)
if b:
return a / float(b) # ensure at least one is a float.
if not a or a != a:
return NaN
return Infinity if a > 0 else -Infinity
# %
def mod_op(self, other):
a = to_number(self)
b = to_number(other)
if abs(a) == Infinity or not b:
return NaN
if abs(b) == Infinity:
return a
pyres = a % b # different signs in python and javascript
# python has the same sign as b and js has the same
# sign as a.
if a < 0 and pyres > 0:
pyres -= abs(b)
elif a > 0 and pyres < 0:
pyres += abs(b)
return float(pyres)
# Comparisons
# <, <=, !=, ==, >=, > are implemented here.
def abstract_relational_comparison(self, other,
self_first=True): # todo speed up!
''' self<other if self_first else other<self.
Returns the result of the question: is self smaller than other?
in case self_first is false it returns the answer of:
is other smaller than self.
result is PyJs type: bool or undefined'''
px = to_primitive(self, 'Number')
py = to_primitive(other, 'Number')
if not self_first: # reverse order
px, py = py, px
if not (Type(px) == 'String' and Type(py) == 'String'):
px, py = to_number(px), to_number(py)
if is_nan(px) or is_nan(py):
return None # watch out here!
return px < py # same cmp algorithm
else:
# I am pretty sure that python has the same
# string cmp algorithm but I have to confirm it
return px < py
# <
def less_op(self, other):
res = abstract_relational_comparison(self, other, True)
if res is None:
return False
return res
# <=
def less_eq_op(self, other):
res = abstract_relational_comparison(self, other, False)
if res is None:
return False
return not res
# >=
def greater_eq_op(self, other):
res = abstract_relational_comparison(self, other, True)
if res is None:
return False
return not res
# >
def greater_op(self, other):
res = abstract_relational_comparison(self, other, False)
if res is None:
return False
return res
# equality
def abstract_equality_op(self, other):
''' returns the result of JS == compare.
result is PyJs type: bool'''
tx, ty = Type(self), Type(other)
if tx == ty:
if tx == 'Undefined' or tx == 'Null':
return True
if tx == 'Number' or tx == 'String' or tx == 'Boolean':
return self == other
return self is other # Object
elif (tx == 'Undefined' and ty == 'Null') or (ty == 'Undefined'
and tx == 'Null'):
return True
elif tx == 'Number' and ty == 'String':
return abstract_equality_op(self, to_number(other))
elif tx == 'String' and ty == 'Number':
return abstract_equality_op(to_number(self), other)
elif tx == 'Boolean':
return abstract_equality_op(to_number(self), other)
elif ty == 'Boolean':
return abstract_equality_op(self, to_number(other))
elif (tx == 'String' or tx == 'Number') and is_object(other):
return abstract_equality_op(self, to_primitive(other))
elif (ty == 'String' or ty == 'Number') and is_object(self):
return abstract_equality_op(to_primitive(self), other)
else:
return False
def abstract_inequality_op(self, other):
return not abstract_equality_op(self, other)
def strict_equality_op(self, other):
typ = Type(self)
if typ != Type(other):
return False
if typ == 'Undefined' or typ == 'Null':
return True
if typ == 'Boolean' or typ == 'String' or typ == 'Number':
return self == other
else: # object
return self is other # Id compare.
def strict_inequality_op(self, other):
return not strict_equality_op(self, other)
def instanceof_op(self, other):
'''checks if self is instance of other'''
if not hasattr(other, 'has_instance'):
return False
return other.has_instance(self)
def in_op(self, other):
'''checks if self is in other'''
if not is_object(other):
raise MakeError(
'TypeError',
"You can\'t use 'in' operator to search in non-objects")
return other.has_property(to_string(self))
BINARY_OPERATIONS = {
'+': add_op,
'-': sub_op,
'*': mul_op,
'/': div_op,
'%': mod_op,
'<<': bit_lshift_op,
'>>': bit_rshift_op,
'>>>': bit_bshift_op,
'|': bit_or_op,
'&': bit_and_op,
'^': bit_xor_op,
'==': abstract_equality_op,
'!=': abstract_inequality_op,
'===': strict_equality_op,
'!==': strict_inequality_op,
'<': less_op,
'<=': less_eq_op,
'>': greater_op,
'>=': greater_eq_op,
'in': in_op,
'instanceof': instanceof_op,
}
@@ -0,0 +1 @@
__author__ = 'Piotr Dabkowski'
@@ -0,0 +1,489 @@
from __future__ import unicode_literals
from ..conversions import *
from ..func_utils import *
from ..operations import strict_equality_op
import six
if six.PY3:
xrange = range
import functools
ARR_STACK = set({})
class ArrayPrototype:
def toString(this, args):
arr = to_object(this, args.space)
func = arr.get('join')
if not is_callable(func):
return u'[object %s]' % GetClass(arr)
return func.call(this, ())
def toLocaleString(this, args):
array = to_object(this, args.space)
arr_len = js_arr_length(array)
# separator is simply a comma ','
if not arr_len:
return ''
res = []
for i in xrange(arr_len):
element = array.get(unicode(i))
if is_undefined(element) or is_null(element):
res.append('')
else:
cand = to_object(element, args.space)
str_func = cand.get('toLocaleString')
if not is_callable(str_func):
raise MakeError(
'TypeError',
'toLocaleString method of item at index %d is not callable'
% i)
res.append(to_string(str_func.call(cand, ())))
return ','.join(res)
def concat(this, args):
array = to_object(this, args.space)
items = [array]
items.extend(tuple(args))
A = []
for E in items:
if GetClass(E) == 'Array':
k = 0
e_len = js_arr_length(E)
while k < e_len:
if E.has_property(unicode(k)):
A.append(E.get(unicode(k)))
k += 1
else:
A.append(E)
return args.space.ConstructArray(A)
def join(this, args):
ARR_STACK.add(this)
array = to_object(this, args.space)
separator = get_arg(args, 0)
arr_len = js_arr_length(array)
separator = ',' if is_undefined(separator) else to_string(separator)
elems = []
for e in xrange(arr_len):
elem = array.get(unicode(e))
if elem in ARR_STACK:
s = ''
else:
s = to_string(elem)
elems.append(
s if not (is_undefined(elem) or is_null(elem)) else '')
res = separator.join(elems)
ARR_STACK.remove(this)
return res
def pop(this, args): #todo check
array = to_object(this, args.space)
arr_len = js_arr_length(array)
if not arr_len:
array.put('length', float(arr_len))
return undefined
ind = unicode(arr_len - 1)
element = array.get(ind)
array.delete(ind)
array.put('length', float(arr_len - 1))
return element
def push(this, args):
array = to_object(this, args.space)
arr_len = js_arr_length(array)
to_put = tuple(args)
i = arr_len
for i, e in enumerate(to_put, arr_len):
array.put(unicode(i), e, True)
array.put('length', float(arr_len + len(to_put)), True)
return float(i)
def reverse(this, args):
array = to_object(this, args.space)
vals = js_array_to_list(array)
has_props = [
array.has_property(unicode(e))
for e in xrange(js_arr_length(array))
]
vals.reverse()
has_props.reverse()
for i, val in enumerate(vals):
if has_props[i]:
array.put(unicode(i), val)
else:
array.delete(unicode(i))
return array
def shift(this, args):
array = to_object(this, args.space)
arr_len = js_arr_length(array)
if not arr_len:
array.put('length', 0.)
return undefined
first = array.get('0')
for k in xrange(1, arr_len):
from_s, to_s = unicode(k), unicode(k - 1)
if array.has_property(from_s):
array.put(to_s, array.get(from_s))
else:
array.delete(to_s)
array.delete(unicode(arr_len - 1))
array.put('length', float(arr_len - 1))
return first
def slice(this, args): # todo check
array = to_object(this, args.space)
start = get_arg(args, 0)
end = get_arg(args, 1)
arr_len = js_arr_length(array)
relative_start = to_int(start)
k = max((arr_len + relative_start), 0) if relative_start < 0 else min(
relative_start, arr_len)
relative_end = arr_len if is_undefined(end) else to_int(end)
final = max((arr_len + relative_end), 0) if relative_end < 0 else min(
relative_end, arr_len)
res = []
n = 0
while k < final:
pk = unicode(k)
if array.has_property(pk):
res.append(array.get(pk))
k += 1
n += 1
return args.space.ConstructArray(res)
def sort(
this, args
): # todo: this assumes array continous (not sparse) - fix for sparse arrays
cmpfn = get_arg(args, 0)
if not GetClass(this) in ('Array', 'Arguments'):
return to_object(this, args.space) # do nothing
arr_len = js_arr_length(this)
if not arr_len:
return this
arr = [
(this.get(unicode(e)) if this.has_property(unicode(e)) else None)
for e in xrange(arr_len)
]
if not is_callable(cmpfn):
cmpfn = None
cmp = lambda a, b: sort_compare(a, b, cmpfn)
if six.PY3:
key = functools.cmp_to_key(cmp)
arr.sort(key=key)
else:
arr.sort(cmp=cmp)
for i in xrange(arr_len):
if arr[i] is None:
this.delete(unicode(i))
else:
this.put(unicode(i), arr[i])
return this
def splice(this, args):
# 1-8
array = to_object(this, args.space)
start = get_arg(args, 0)
deleteCount = get_arg(args, 1)
arr_len = js_arr_length(this)
relative_start = to_int(start)
actual_start = max(
(arr_len + relative_start), 0) if relative_start < 0 else min(
relative_start, arr_len)
actual_delete_count = min(
max(to_int(deleteCount), 0), arr_len - actual_start)
k = 0
A = args.space.NewArray(0)
# 9
while k < actual_delete_count:
if array.has_property(unicode(actual_start + k)):
A.put(unicode(k), array.get(unicode(actual_start + k)))
k += 1
# 10-11
items = list(args)[2:]
items_len = len(items)
# 12
if items_len < actual_delete_count:
k = actual_start
while k < (arr_len - actual_delete_count):
fr = unicode(k + actual_delete_count)
to = unicode(k + items_len)
if array.has_property(fr):
array.put(to, array.get(fr))
else:
array.delete(to)
k += 1
k = arr_len
while k > (arr_len - actual_delete_count + items_len):
array.delete(unicode(k - 1))
k -= 1
# 13
elif items_len > actual_delete_count:
k = arr_len - actual_delete_count
while k > actual_start:
fr = unicode(k + actual_delete_count - 1)
to = unicode(k + items_len - 1)
if array.has_property(fr):
array.put(to, array.get(fr))
else:
array.delete(to)
k -= 1
# 14-17
k = actual_start
while items:
E = items.pop(0)
array.put(unicode(k), E)
k += 1
array.put('length', float(arr_len - actual_delete_count + items_len))
return A
def unshift(this, args):
array = to_object(this, args.space)
arr_len = js_arr_length(array)
argCount = len(args)
k = arr_len
while k > 0:
fr = unicode(k - 1)
to = unicode(k + argCount - 1)
if array.has_property(fr):
array.put(to, array.get(fr))
else:
array.delete(to)
k -= 1
items = tuple(args)
for j, e in enumerate(items):
array.put(unicode(j), e)
array.put('length', float(arr_len + argCount))
return float(arr_len + argCount)
def indexOf(this, args):
array = to_object(this, args.space)
searchElement = get_arg(args, 0)
arr_len = js_arr_length(array)
if arr_len == 0:
return -1.
if len(args) > 1:
n = to_int(args[1])
else:
n = 0
if n >= arr_len:
return -1.
if n >= 0:
k = n
else:
k = arr_len - abs(n)
if k < 0:
k = 0
while k < arr_len:
if array.has_property(unicode(k)):
elementK = array.get(unicode(k))
if strict_equality_op(searchElement, elementK):
return float(k)
k += 1
return -1.
def lastIndexOf(this, args):
array = to_object(this, args.space)
searchElement = get_arg(args, 0)
arr_len = js_arr_length(array)
if arr_len == 0:
return -1.
if len(args) > 1:
n = to_int(args[1])
else:
n = arr_len - 1
if n >= 0:
k = min(n, arr_len - 1)
else:
k = arr_len - abs(n)
while k >= 0:
if array.has_property(unicode(k)):
elementK = array.get(unicode(k))
if strict_equality_op(searchElement, elementK):
return float(k)
k -= 1
return -1.
def every(this, args):
array = to_object(this, args.space)
callbackfn = get_arg(args, 0)
arr_len = js_arr_length(array)
if not is_callable(callbackfn):
raise MakeError('TypeError', 'callbackfn must be a function')
T = get_arg(args, 1)
k = 0
while k < arr_len:
if array.has_property(unicode(k)):
kValue = array.get(unicode(k))
if not to_boolean(
callbackfn.call(T, (kValue, float(k), array))):
return False
k += 1
return True
def some(this, args):
array = to_object(this, args.space)
callbackfn = get_arg(args, 0)
arr_len = js_arr_length(array)
if not is_callable(callbackfn):
raise MakeError('TypeError', 'callbackfn must be a function')
T = get_arg(args, 1)
k = 0
while k < arr_len:
if array.has_property(unicode(k)):
kValue = array.get(unicode(k))
if to_boolean(callbackfn.call(T, (kValue, float(k), array))):
return True
k += 1
return False
def forEach(this, args):
array = to_object(this, args.space)
callbackfn = get_arg(args, 0)
arr_len = js_arr_length(array)
if not is_callable(callbackfn):
raise MakeError('TypeError', 'callbackfn must be a function')
_this = get_arg(args, 1)
k = 0
while k < arr_len:
sk = unicode(k)
if array.has_property(sk):
kValue = array.get(sk)
callbackfn.call(_this, (kValue, float(k), array))
k += 1
return undefined
def map(this, args):
array = to_object(this, args.space)
callbackfn = get_arg(args, 0)
arr_len = js_arr_length(array)
if not is_callable(callbackfn):
raise MakeError('TypeError', 'callbackfn must be a function')
_this = get_arg(args, 1)
k = 0
A = args.space.NewArray(0)
while k < arr_len:
Pk = unicode(k)
if array.has_property(Pk):
kValue = array.get(Pk)
mappedValue = callbackfn.call(_this, (kValue, float(k), array))
A.define_own_property(
Pk, {
'value': mappedValue,
'writable': True,
'enumerable': True,
'configurable': True
}, False)
k += 1
return A
def filter(this, args):
array = to_object(this, args.space)
callbackfn = get_arg(args, 0)
arr_len = js_arr_length(array)
if not is_callable(callbackfn):
raise MakeError('TypeError', 'callbackfn must be a function')
_this = get_arg(args, 1)
k = 0
res = []
while k < arr_len:
if array.has_property(unicode(k)):
kValue = array.get(unicode(k))
if to_boolean(
callbackfn.call(_this, (kValue, float(k), array))):
res.append(kValue)
k += 1
return args.space.ConstructArray(res)
def reduce(this, args):
array = to_object(this, args.space)
callbackfn = get_arg(args, 0)
arr_len = js_arr_length(array)
if not is_callable(callbackfn):
raise MakeError('TypeError', 'callbackfn must be a function')
if not arr_len and len(args) < 2:
raise MakeError('TypeError',
'Reduce of empty array with no initial value')
k = 0
accumulator = undefined
if len(args) > 1: # initial value present
accumulator = args[1]
else:
kPresent = False
while not kPresent and k < arr_len:
kPresent = array.has_property(unicode(k))
if kPresent:
accumulator = array.get(unicode(k))
k += 1
if not kPresent:
raise MakeError('TypeError',
'Reduce of empty array with no initial value')
while k < arr_len:
if array.has_property(unicode(k)):
kValue = array.get(unicode(k))
accumulator = callbackfn.call(
undefined, (accumulator, kValue, float(k), array))
k += 1
return accumulator
def reduceRight(this, args):
array = to_object(this, args.space)
callbackfn = get_arg(args, 0)
arr_len = js_arr_length(array)
if not is_callable(callbackfn):
raise MakeError('TypeError', 'callbackfn must be a function')
if not arr_len and len(args) < 2:
raise MakeError('TypeError',
'Reduce of empty array with no initial value')
k = arr_len - 1
accumulator = undefined
if len(args) > 1: # initial value present
accumulator = args[1]
else:
kPresent = False
while not kPresent and k >= 0:
kPresent = array.has_property(unicode(k))
if kPresent:
accumulator = array.get(unicode(k))
k -= 1
if not kPresent:
raise MakeError('TypeError',
'Reduce of empty array with no initial value')
while k >= 0:
if array.has_property(unicode(k)):
kValue = array.get(unicode(k))
accumulator = callbackfn.call(
undefined, (accumulator, kValue, float(k), array))
k -= 1
return accumulator
def sort_compare(a, b, comp):
if a is None:
if b is None:
return 0
return 1
if b is None:
if a is None:
return 0
return -1
if is_undefined(a):
if is_undefined(b):
return 0
return 1
if is_undefined(b):
if is_undefined(a):
return 0
return -1
if comp is not None:
res = comp.call(undefined, (a, b))
return to_int(res)
x, y = to_string(a), to_string(b)
if x < y:
return -1
elif x > y:
return 1
return 0
@@ -0,0 +1,22 @@
from __future__ import unicode_literals
from ..conversions import *
from ..func_utils import *
class BooleanPrototype:
def toString(this, args):
if GetClass(this) != 'Boolean':
raise MakeError('TypeError',
'Boolean.prototype.toString is not generic')
if is_object(this):
this = this.value
return u'true' if this else u'false'
def valueOf(this, args):
if GetClass(this) != 'Boolean':
raise MakeError('TypeError',
'Boolean.prototype.valueOf is not generic')
if is_object(this):
this = this.value
return this
@@ -0,0 +1,15 @@
from __future__ import unicode_literals
from ..conversions import *
from ..func_utils import *
class ErrorPrototype:
def toString(this, args):
if Type(this) != 'Object':
raise MakeError('TypeError',
'Error.prototype.toString called on non-object')
name = this.get('name')
name = u'Error' if is_undefined(name) else to_string(name)
msg = this.get('message')
msg = '' if is_undefined(msg) else to_string(msg)
return name + (name and msg and ': ') + msg
@@ -0,0 +1,61 @@
from __future__ import unicode_literals
from ..conversions import *
from ..func_utils import *
# python 3 support
import six
if six.PY3:
basestring = str
long = int
xrange = range
unicode = str
# todo fix apply and bind
class FunctionPrototype:
def toString(this, args):
if not is_callable(this):
raise MakeError('TypeError',
'Function.prototype.toString is not generic')
args = u', '.join(map(unicode, this.params))
return u'function %s(%s) { [native code] }' % (this.name if this.name
else u'', args)
def call(this, args):
if not is_callable(this):
raise MakeError('TypeError',
'Function.prototype.call is not generic')
_this = get_arg(args, 0)
_args = tuple(args)[1:]
return this.call(_this, _args)
def apply(this, args):
if not is_callable(this):
raise MakeError('TypeError',
'Function.prototype.apply is not generic')
_args = get_arg(args, 1)
if not is_object(_args):
raise MakeError(
'TypeError',
'argList argument to Function.prototype.apply must an Object')
_this = get_arg(args, 0)
return this.call(_this, js_array_to_tuple(_args))
def bind(this, args):
if not is_callable(this):
raise MakeError('TypeError',
'Function.prototype.bind is not generic')
bound_this = get_arg(args, 0)
bound_args = tuple(args)[1:]
def bound(dummy_this, extra_args):
return this.call(bound_this, bound_args + tuple(extra_args))
js_bound = args.space.NewFunction(bound, this.ctx, (), u'', False, ())
js_bound.put(u'length',
float(max(len(this.params) - len(bound_args), 0.)))
js_bound.put(u'name', u'boundFunc')
return js_bound
@@ -0,0 +1,205 @@
from __future__ import unicode_literals
from ..conversions import *
from ..func_utils import *
from ..operations import strict_equality_op
import json
indent = ''
# python 3 support
import six
if six.PY3:
basestring = str
long = int
xrange = range
unicode = str
def parse(this, args):
text, reviver = get_arg(args, 0), get_arg(args, 1)
s = to_string(text)
try:
unfiltered = json.loads(s)
except:
raise MakeError(
'SyntaxError',
'JSON.parse could not parse JSON string - Invalid syntax')
unfiltered = to_js(unfiltered, args.space)
if is_callable(reviver):
root = args.space.ConstructObject({'': unfiltered})
return walk(root, '', reviver)
else:
return unfiltered
def stringify(this, args):
global indent
value, replacer, space = get_arg(args, 0), get_arg(args, 1), get_arg(
args, 2)
stack = set([])
indent = ''
property_list = replacer_function = undefined
if is_object(replacer):
if is_callable(replacer):
replacer_function = replacer
elif replacer.Class == 'Array':
property_list = []
for e in replacer:
v = replacer[e]
item = undefined
typ = Type(v)
if typ == 'Number':
item = to_string(v)
elif typ == 'String':
item = v
elif typ == 'Object':
if GetClass(v) in ('String', 'Number'):
item = to_string(v)
if not is_undefined(item) and item not in property_list:
property_list.append(item)
if is_object(space):
if GetClass(space) == 'Number':
space = to_number(space)
elif GetClass(space) == 'String':
space = to_string(space)
if Type(space) == 'Number':
space = min(10, to_int(space))
gap = max(int(space), 0) * ' '
elif Type(space) == 'String':
gap = space[:10]
else:
gap = ''
return Str('', args.space.ConstructObject({
'': value
}), replacer_function, property_list, gap, stack, space)
def Str(key, holder, replacer_function, property_list, gap, stack, space):
value = holder.get(key)
if is_object(value):
to_json = value.get('toJSON')
if is_callable(to_json):
value = to_json.call(value, (key, ))
if not is_undefined(replacer_function):
value = replacer_function.call(holder, (key, value))
if is_object(value):
if value.Class == 'String':
value = to_string(value)
elif value.Class == 'Number':
value = to_number(value)
elif value.Class == 'Boolean':
value = to_boolean(value)
typ = Type(value)
if is_null(value):
return 'null'
elif typ == 'Boolean':
return 'true' if value else 'false'
elif typ == 'String':
return Quote(value)
elif typ == 'Number':
if not is_infinity(value):
return to_string(value)
return 'null'
if is_object(value) and not is_callable(value):
if value.Class == 'Array':
return ja(value, stack, gap, property_list, replacer_function,
space)
else:
return jo(value, stack, gap, property_list, replacer_function,
space)
return undefined
def jo(value, stack, gap, property_list, replacer_function, space):
global indent
if value in stack:
raise MakeError('TypeError', 'Converting circular structure to JSON')
stack.add(value)
stepback = indent
indent += gap
if not is_undefined(property_list):
k = property_list
else:
k = [unicode(e) for e, d in value.own.items() if d.get('enumerable')]
partial = []
for p in k:
str_p = Str(p, value, replacer_function, property_list, gap, stack,
space)
if not is_undefined(str_p):
member = json.dumps(p) + ':' + (
' ' if gap else
'') + str_p # todo not sure here - what space character?
partial.append(member)
if not partial:
final = '{}'
else:
if not gap:
final = '{%s}' % ','.join(partial)
else:
sep = ',\n' + indent
properties = sep.join(partial)
final = '{\n' + indent + properties + '\n' + stepback + '}'
stack.remove(value)
indent = stepback
return final
def ja(value, stack, gap, property_list, replacer_function, space):
global indent
if value in stack:
raise MakeError('TypeError', 'Converting circular structure to JSON')
stack.add(value)
stepback = indent
indent += gap
partial = []
length = js_arr_length(value)
for index in xrange(length):
index = unicode(index)
str_index = Str(index, value, replacer_function, property_list, gap,
stack, space)
if is_undefined(str_index):
partial.append('null')
else:
partial.append(str_index)
if not partial:
final = '[]'
else:
if not gap:
final = '[%s]' % ','.join(partial)
else:
sep = ',\n' + indent
properties = sep.join(partial)
final = '[\n' + indent + properties + '\n' + stepback + ']'
stack.remove(value)
indent = stepback
return final
def Quote(string):
return json.dumps(string)
def to_js(d, _args_space):
return convert_to_js_type(d, _args_space)
def walk(holder, name, reviver):
val = holder.get(name)
if GetClass(val) == 'Array':
for i in xrange(js_arr_length(val)):
i = unicode(i)
new_element = walk(val, i, reviver)
if is_undefined(new_element):
val.delete(i)
else:
new_element.put(i, new_element)
elif is_object(val):
for key in [
unicode(e) for e, d in val.own.items() if d.get('enumerable')
]:
new_element = walk(val, key, reviver)
if is_undefined(new_element):
val.delete(key)
else:
val.put(key, new_element)
return reviver.call(holder, (name, val))
@@ -0,0 +1,163 @@
from __future__ import unicode_literals
from ..conversions import *
from ..func_utils import *
import six
if six.PY3:
basestring = str
long = int
xrange = range
unicode = str
RADIX_SYMBOLS = {
0: '0',
1: '1',
2: '2',
3: '3',
4: '4',
5: '5',
6: '6',
7: '7',
8: '8',
9: '9',
10: 'a',
11: 'b',
12: 'c',
13: 'd',
14: 'e',
15: 'f',
16: 'g',
17: 'h',
18: 'i',
19: 'j',
20: 'k',
21: 'l',
22: 'm',
23: 'n',
24: 'o',
25: 'p',
26: 'q',
27: 'r',
28: 's',
29: 't',
30: 'u',
31: 'v',
32: 'w',
33: 'x',
34: 'y',
35: 'z'
}
def to_str_rep(num):
if is_nan(num):
return 'NaN'
elif is_infinity(num):
sign = '-' if num < 0 else ''
return sign + 'Infinity'
elif int(num) == num: # dont print .0
return unicode(int(num))
return unicode(num) # todo: Make it 100% consistent with Node
class NumberPrototype:
def toString(this, args):
if GetClass(this) != 'Number':
raise MakeError('TypeError',
'Number.prototype.valueOf is not generic')
if type(this) != float:
this = this.value
radix = get_arg(args, 0)
if is_undefined(radix):
return to_str_rep(this)
r = to_int(radix)
if r == 10:
return to_str_rep(this)
if r not in xrange(2, 37) or radix != r:
raise MakeError(
'RangeError',
'Number.prototype.toString() radix argument must be an integer between 2 and 36'
)
num = to_int(this)
if num < 0:
num = -num
sign = '-'
else:
sign = ''
res = ''
while num:
s = RADIX_SYMBOLS[num % r]
num = num // r
res = s + res
return sign + (res if res else '0')
def valueOf(this, args):
if GetClass(this) != 'Number':
raise MakeError('TypeError',
'Number.prototype.valueOf is not generic')
if type(this) != float:
this = this.value
return this
def toFixed(this, args):
if GetClass(this) != 'Number':
raise MakeError(
'TypeError',
'Number.prototype.toFixed called on incompatible receiver')
if type(this) != float:
this = this.value
fractionDigits = get_arg(args, 0)
digs = to_int(fractionDigits)
if digs < 0 or digs > 20:
raise MakeError(
'RangeError',
'toFixed() digits argument must be between 0 and 20')
elif is_infinity(this):
return 'Infinity' if this > 0 else '-Infinity'
elif is_nan(this):
return 'NaN'
return format(this, '-.%df' % digs)
def toExponential(this, args):
if GetClass(this) != 'Number':
raise MakeError(
'TypeError',
'Number.prototype.toExponential called on incompatible receiver'
)
if type(this) != float:
this = this.value
fractionDigits = get_arg(args, 0)
digs = to_int(fractionDigits)
if digs < 0 or digs > 20:
raise MakeError(
'RangeError',
'toFixed() digits argument must be between 0 and 20')
elif is_infinity(this):
return 'Infinity' if this > 0 else '-Infinity'
elif is_nan(this):
return 'NaN'
return format(this, '-.%de' % digs)
def toPrecision(this, args):
if GetClass(this) != 'Number':
raise MakeError(
'TypeError',
'Number.prototype.toPrecision called on incompatible receiver')
if type(this) != float:
this = this.value
precision = get_arg(args, 0)
if is_undefined(precision):
return to_string(this)
prec = to_int(precision)
if is_nan(this):
return 'NaN'
elif is_infinity(this):
return 'Infinity' if this > 0 else '-Infinity'
digs = prec - len(str(int(this)))
if digs >= 0:
return format(this, '-.%df' % digs)
else:
return format(this, '-.%df' % (prec - 1))
NumberPrototype.toLocaleString = NumberPrototype.toString
@@ -0,0 +1,48 @@
from __future__ import unicode_literals
from ..conversions import *
from ..func_utils import *
class ObjectPrototype:
def toString(this, args):
if type(this) == UNDEFINED_TYPE:
return u'[object Undefined]'
elif type(this) == NULL_TYPE:
return u'[object Null]'
return u'[object %s]' % GetClass(to_object(this, args.space))
def valueOf(this, args):
return to_object(this, args.space)
def toLocaleString(this, args):
o = to_object(this, args.space)
toString = o.get(u'toString')
if not is_callable(toString):
raise MakeError('TypeError', 'toString of this is not callcable')
else:
return toString.call(this, args)
def hasOwnProperty(this, args):
prop = get_arg(args, 0)
o = to_object(this, args.space)
return o.get_own_property(to_string(prop)) is not None
def isPrototypeOf(this, args):
# a bit stupid specification because of object conversion that will cause invalid values for primitives
# for example Object.prototype.isPrototypeOf.call((5).__proto__, 5) gives false
obj = get_arg(args, 0)
if not is_object(obj):
return False
o = to_object(this, args.space)
while 1:
obj = obj.prototype
if obj is None or is_null(obj):
return False
if obj is o:
return True
def propertyIsEnumerable(this, args):
prop = get_arg(args, 0)
o = to_object(this, args.space)
cand = o.own.get(to_string(prop))
return cand is not None and cand.get('enumerable')
@@ -0,0 +1,56 @@
from __future__ import unicode_literals
from ..conversions import *
from ..func_utils import *
class RegExpPrototype:
def toString(this, args):
flags = u''
try:
if this.glob:
flags += u'g'
if this.ignore_case:
flags += u'i'
if this.multiline:
flags += u'm'
except:
pass
try:
v = this.value if this.value else u'(?:)'
except:
v = u'(?:)'
return u'/%s/' % v + flags
def test(this, args):
string = get_arg(args, 0)
return RegExpExec(this, string, args.space) is not null
def _exec(
this, args
): # will be changed to exec in base.py. cant name it exec here...
string = get_arg(args, 0)
return RegExpExec(this, string, args.space)
def RegExpExec(this, string, space):
if GetClass(this) != 'RegExp':
raise MakeError('TypeError', 'RegExp.prototype.exec is not generic!')
string = to_string(string)
length = len(string)
i = to_int(this.get('lastIndex')) if this.glob else 0
matched = False
while not matched:
if i < 0 or i > length:
this.put('lastIndex', 0.)
return null
matched = this.match(string, i)
i += 1
start, end = matched.span() #[0]+i-1, matched.span()[1]+i-1
if this.glob:
this.put('lastIndex', float(end))
arr = convert_to_js_type(
[matched.group()] + list(matched.groups()), space=space)
arr.put('index', float(start))
arr.put('input', unicode(string))
return arr
@@ -0,0 +1,323 @@
from __future__ import unicode_literals
# -*- coding: utf-8 -*-
import re
from ..conversions import *
from ..func_utils import *
from .jsregexp import RegExpExec
DIGS = set(u'0123456789')
WHITE = u"\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF"
def replacement_template(rep, source, span, npar):
"""Takes the replacement template and some info about the match and returns filled template
"""
n = 0
res = ''
while n < len(rep) - 1:
char = rep[n]
if char == '$':
if rep[n + 1] == '$':
res += '$'
n += 2
continue
elif rep[n + 1] == '`':
# replace with string that is BEFORE match
res += source[:span[0]]
n += 2
continue
elif rep[n + 1] == '\'':
# replace with string that is AFTER match
res += source[span[1]:]
n += 2
continue
elif rep[n + 1] in DIGS:
dig = rep[n + 1]
if n + 2 < len(rep) and rep[n + 2] in DIGS:
dig += rep[n + 2]
num = int(dig)
# we will not do any replacements if we dont have this npar or dig is 0
if not num or num > len(npar):
res += '$' + dig
else:
# None - undefined has to be replaced with ''
res += npar[num - 1] if npar[num - 1] else ''
n += 1 + len(dig)
continue
res += char
n += 1
if n < len(rep):
res += rep[-1]
return res
###################################################
class StringPrototype:
def toString(this, args):
if GetClass(this) != 'String':
raise MakeError('TypeError',
'String.prototype.toString is not generic')
if type(this) == unicode:
return this
assert type(this.value) == unicode
return this.value
def valueOf(this, args):
if GetClass(this) != 'String':
raise MakeError('TypeError',
'String.prototype.valueOf is not generic')
if type(this) == unicode:
return this
assert type(this.value) == unicode
return this.value
def charAt(this, args):
cok(this)
pos = to_int(get_arg(args, 0))
s = to_string(this)
if 0 <= pos < len(s):
return s[pos]
return u''
def charCodeAt(this, args):
cok(this)
pos = to_int(get_arg(args, 0))
s = to_string(this)
if 0 <= pos < len(s):
return float(ord(s[pos]))
return NaN
def concat(this, args):
cok(this)
return to_string(this) + u''.join(map(to_string, args))
def indexOf(this, args):
cok(this)
search = to_string(get_arg(args, 0))
pos = to_int(get_arg(args, 1))
s = to_string(this)
return float(s.find(search, min(max(pos, 0), len(s))))
def lastIndexOf(this, args):
cok(this)
search = to_string(get_arg(args, 0))
pos = get_arg(args, 1)
s = to_string(this)
pos = 10**12 if is_nan(pos) else to_int(pos)
return float(s.rfind(search, 0, min(max(pos, 0) + 1, len(s))))
def localeCompare(this, args):
cok(this)
s = to_string(this)
that = to_string(get_arg(args, 0))
if s < that:
return -1.
elif s > that:
return 1.
return 0.
def match(this, args):
cok(this)
s = to_string(this)
regexp = get_arg(args, 0)
r = args.space.NewRegExp(
regexp, '') if GetClass(regexp) != 'RegExp' else regexp
if not r.glob:
return RegExpExec(r, s, space=args.space)
r.put('lastIndex', float(0))
found = []
previous_last_index = 0
last_match = True
while last_match:
result = RegExpExec(r, s, space=args.space)
if is_null(result):
last_match = False
else:
this_index = r.get('lastIndex')
if this_index == previous_last_index:
r.put('lastIndex', float(this_index + 1))
previous_last_index += 1
else:
previous_last_index = this_index
matchStr = result.get('0')
found.append(matchStr)
if not found:
return null
return args.space.ConstructArray(found)
def replace(this, args):
# VERY COMPLICATED. to check again.
cok(this)
s = to_string(this)
searchValue = get_arg(args, 0)
replaceValue = get_arg(args, 1)
res = ''
if not is_callable(replaceValue):
replaceValue = to_string(replaceValue)
func = False
else:
func = True
# Replace all ( global )
if GetClass(searchValue) == 'RegExp' and searchValue.glob:
last = 0
for e in re.finditer(searchValue.pat, s):
res += s[last:e.span()[0]]
if func:
# prepare arguments for custom func (replaceValue)
call_args = (e.group(), ) + e.groups() + (e.span()[1], s)
# convert all types to JS before Js bytecode call...
res += to_string(
replaceValue.call(
this, ensure_js_types(call_args,
space=args.space)))
else:
res += replacement_template(replaceValue, s, e.span(),
e.groups())
last = e.span()[1]
res += s[last:]
return res
elif GetClass(searchValue) == 'RegExp':
e = re.search(searchValue.pat, s)
if e is None:
return s
span = e.span()
pars = e.groups()
match = e.group()
else:
match = to_string(searchValue)
ind = s.find(match)
if ind == -1:
return s
span = ind, ind + len(match)
pars = ()
res = s[:span[0]]
if func:
call_args = (match, ) + pars + (span[1], s)
# convert all types to JS before Js bytecode call...
res += to_string(
replaceValue.call(this,
ensure_js_types(call_args,
space=args.space)))
else:
res += replacement_template(replaceValue, s, span, pars)
res += s[span[1]:]
return res
def search(this, args):
cok(this)
string = to_string(this)
regexp = get_arg(args, 0)
if GetClass(regexp) == 'RegExp':
rx = regexp
else:
rx = args.space.NewRegExp(regexp, '')
res = re.search(rx.pat, string)
if res is not None:
return float(res.span()[0])
return -1.
def slice(this, args):
cok(this)
s = to_string(this)
start = to_int(get_arg(args, 0))
length = len(s)
end = get_arg(args, 1)
end = length if is_undefined(end) else to_int(end)
#From = max(length+start, 0) if start<0 else min(length, start)
#To = max(length+end, 0) if end<0 else min(length, end)
return s[start:end]
def split(this, args):
# its a bit different from re.split!
cok(this)
s = to_string(this)
separator = get_arg(args, 0)
limit = get_arg(args, 1)
lim = 2**32 - 1 if is_undefined(limit) else to_uint32(limit)
if not lim:
return args.space.ConstructArray([])
if is_undefined(separator):
return args.space.ConstructArray([s])
len_s = len(s)
res = []
R = separator if GetClass(separator) == 'RegExp' else to_string(
separator)
if not len_s:
if SplitMatch(s, 0, R) is None:
return args.space.ConstructArray([s])
return args.space.ConstructArray([])
p = q = 0
while q != len_s:
e, cap = SplitMatch(s, q, R)
if e is None or e == p:
q += 1
continue
res.append(s[p:q])
p = q = e
if len(res) == lim:
return args.space.ConstructArray(res)
for element in cap:
res.append(element)
if len(res) == lim:
return args.space.ConstructArray(res)
res.append(s[p:])
return args.space.ConstructArray(res)
def substring(this, args):
cok(this)
s = to_string(this)
start = to_int(get_arg(args, 0))
length = len(s)
end = get_arg(args, 1)
end = length if is_undefined(end) else to_int(end)
fstart = min(max(start, 0), length)
fend = min(max(end, 0), length)
return s[min(fstart, fend):max(fstart, fend)]
def substr(this, args):
cok(this)
#I hate this function and its description in specification
r1 = to_string(this)
r2 = to_int(get_arg(args, 0))
length = get_arg(args, 1)
r3 = 10**20 if is_undefined(length) else to_int(length)
r4 = len(r1)
r5 = r2 if r2 >= 0 else max(0, r2 + r4)
r6 = min(max(r3, 0), r4 - r5)
if r6 <= 0:
return ''
return r1[r5:r5 + r6]
def toLowerCase(this, args):
cok(this)
return to_string(this).lower()
def toLocaleLowerCase(this, args):
cok(this)
return to_string(this).lower()
def toUpperCase(this, args):
cok(this)
return to_string(this).upper()
def toLocaleUpperCase(this, args):
cok(this)
return to_string(this).upper()
def trim(this, args):
cok(this)
return to_string(this).strip(WHITE)
def SplitMatch(s, q, R):
# s is Py String to match, q is the py int match start and R is Js RegExp or String.
if GetClass(R) == 'RegExp':
res = R.match(s, q)
return (None, ()) if res is None else (res.span()[1], res.groups())
# R is just a string
if s[q:].startswith(R):
return q + len(R), ()
return None, ()
@@ -0,0 +1,149 @@
from __future__ import unicode_literals
from ..conversions import *
from ..func_utils import *
RADIX_CHARS = {
'1': 1,
'0': 0,
'3': 3,
'2': 2,
'5': 5,
'4': 4,
'7': 7,
'6': 6,
'9': 9,
'8': 8,
'a': 10,
'c': 12,
'b': 11,
'e': 14,
'd': 13,
'g': 16,
'f': 15,
'i': 18,
'h': 17,
'k': 20,
'j': 19,
'm': 22,
'l': 21,
'o': 24,
'n': 23,
'q': 26,
'p': 25,
's': 28,
'r': 27,
'u': 30,
't': 29,
'w': 32,
'v': 31,
'y': 34,
'x': 33,
'z': 35,
'A': 10,
'C': 12,
'B': 11,
'E': 14,
'D': 13,
'G': 16,
'F': 15,
'I': 18,
'H': 17,
'K': 20,
'J': 19,
'M': 22,
'L': 21,
'O': 24,
'N': 23,
'Q': 26,
'P': 25,
'S': 28,
'R': 27,
'U': 30,
'T': 29,
'W': 32,
'V': 31,
'Y': 34,
'X': 33,
'Z': 35
}
# parseFloat
# parseInt
# isFinite
# isNaN
def parseInt(this, args):
string, radix = get_arg(args, 0), get_arg(args, 1)
string = to_string(string).lstrip()
sign = 1
if string and string[0] in ('+', '-'):
if string[0] == '-':
sign = -1
string = string[1:]
r = to_int32(radix)
strip_prefix = True
if r:
if r < 2 or r > 36:
return NaN
if r != 16:
strip_prefix = False
else:
r = 10
if strip_prefix:
if len(string) >= 2 and string[:2] in ('0x', '0X'):
string = string[2:]
r = 16
n = 0
num = 0
while n < len(string):
cand = RADIX_CHARS.get(string[n])
if cand is None or not cand < r:
break
num = cand + num * r
n += 1
if not n:
return NaN
return float(sign * num)
def parseFloat(this, args):
string = get_arg(args, 0)
string = to_string(string).strip()
sign = 1
if string and string[0] in ('+', '-'):
if string[0] == '-':
sign = -1
string = string[1:]
num = None
length = 1
max_len = None
failed = 0
while length <= len(string):
try:
num = float(string[:length])
max_len = length
failed = 0
except:
failed += 1
if failed > 4: # cant be a number anymore
break
length += 1
if num is None:
return NaN
return sign * float(string[:max_len])
def isNaN(this, args):
number = get_arg(args, 0)
if is_nan(to_number(number)):
return True
return False
def isFinite(this, args):
number = get_arg(args, 0)
num = to_number(number)
if is_nan(num) or is_infinity(num):
return False
return True
@@ -0,0 +1,33 @@
import pyjsparser
from .space import Space
from . import fill_space
from .byte_trans import ByteCodeGenerator
from .code import Code
from .simplex import *
pyjsparser.parser.ENABLE_JS2PY_ERRORS = lambda msg: MakeError(u'SyntaxError', unicode(msg))
def get_js_bytecode(js):
a = ByteCodeGenerator(Code())
d = pyjsparser.parse(js)
a.emit(d)
return a.exe.tape
def eval_js_vm(js, debug=False):
a = ByteCodeGenerator(Code(debug_mode=debug))
s = Space()
a.exe.space = s
s.exe = a.exe
d = pyjsparser.parse(js)
a.emit(d)
fill_space.fill_space(s, a)
if debug:
from pprint import pprint
pprint(a.exe.tape)
print()
a.exe.compile()
return a.exe.run(a.exe.space.GlobalObj)
@@ -0,0 +1,160 @@
from __future__ import unicode_literals
import six
if six.PY3:
basestring = str
long = int
xrange = range
unicode = str
#Undefined
class PyJsUndefined(object):
TYPE = 'Undefined'
Class = 'Undefined'
undefined = PyJsUndefined()
#Null
class PyJsNull(object):
TYPE = 'Null'
Class = 'Null'
null = PyJsNull()
Infinity = float('inf')
NaN = float('nan')
UNDEFINED_TYPE = PyJsUndefined
NULL_TYPE = PyJsNull
STRING_TYPE = unicode if six.PY2 else str
NUMBER_TYPE = float
BOOLEAN_TYPE = bool
# exactly 5 simplexes!
PRIMITIVES = frozenset(
[UNDEFINED_TYPE, NULL_TYPE, STRING_TYPE, NUMBER_TYPE, BOOLEAN_TYPE])
TYPE_NAMES = {
UNDEFINED_TYPE: 'Undefined',
NULL_TYPE: 'Null',
STRING_TYPE: 'String',
NUMBER_TYPE: 'Number',
BOOLEAN_TYPE: 'Boolean',
}
def Type(x):
# Any -> Str
return TYPE_NAMES.get(type(x), 'Object')
def GetClass(x):
# Any -> Str
cand = TYPE_NAMES.get(type(x))
if cand is None:
return x.Class
return cand
def is_undefined(self):
return self is undefined
def is_null(self):
return self is null
def is_primitive(self):
return type(self) in PRIMITIVES
def is_object(self):
return not is_primitive(self)
def is_callable(self):
return hasattr(self, 'call')
def is_infinity(self):
return self == Infinity or self == -Infinity
def is_nan(self):
return self != self # nan!=nan evaluates to True
def is_finite(self):
return not (is_nan(self) or is_infinity(self))
class JsException(Exception):
def __init__(self, typ=None, message=None, throw=None):
if typ is None and message is None and throw is None:
# it means its the trasnlator based error (old format), do nothing
self._translator_based = True
else:
assert throw is None or (typ is None
and message is None), (throw, typ,
message)
self._translator_based = False
self.typ = typ
self.message = message
self.throw = throw
def get_thrown_value(self, space):
if self.throw is not None:
return self.throw
else:
return space.NewError(self.typ, self.message)
def __str__(self):
if self._translator_based:
if self.mes.Class == 'Error':
return self.mes.callprop('toString').value
else:
return self.mes.to_string().value
else:
if self.throw is not None:
from .conversions import to_string
return to_string(self.throw)
else:
return self.typ + ': ' + self.message
def MakeError(typ, message=u'no info', throw=None):
return JsException(typ,
unicode(message) if message is not None else message,
throw)
def value_from_js_exception(js_exception, space):
if js_exception.throw is not None:
return js_exception.throw
else:
return space.NewError(js_exception.typ, js_exception.message)
def js_dtoa(number):
if is_nan(number):
return u'NaN'
elif is_infinity(number):
if number > 0:
return u'Infinity'
return u'-Infinity'
elif number == 0.:
return u'0'
elif abs(number) < 1e-6 or abs(number) >= 1e21:
frac, exponent = unicode(repr(float(number))).split('e')
# Remove leading zeros from the exponent.
exponent = int(exponent)
return frac + ('e' if exponent < 0 else 'e+') + unicode(exponent)
elif abs(number) < 1e-4: # python starts to return exp notation while we still want the prec
frac, exponent = unicode(repr(float(number))).split('e-')
base = u'0.' + u'0' * (int(exponent) - 1) + frac.lstrip('-').replace('.', '')
return base if number > 0. else u'-' + base
elif isinstance(number, long) or number.is_integer(): # dont print .0
return unicode(int(number))
return unicode(repr(number)) # python representation should be equivalent.
@@ -0,0 +1,92 @@
from .base import *
from .simplex import *
class Space(object):
def __init__(self):
self.GlobalObj = None
self.ctx = None
self.byte_generator = None
self.Number = None
self.String = None
self.Boolean = None
self.RegExp = None
self.Object = None
self.Array = None
self.Function = None
self.BooleanPrototype = None
self.NumberPrototype = None
self.StringPrototype = None
self.FunctionPrototype = None
self.ArrayPrototype = None
self.RegExpPrototype = None
self.DatePrototype = None
self.ObjectPrototype = None
self.ErrorPrototype = None
self.EvalErrorPrototype = None
self.RangeErrorPrototype = None
self.ReferenceErrorPrototype = None
self.SyntaxErrorPrototype = None
self.TypeErrorPrototype = None
self.URIErrorPrototype = None
self.interpreter = None
@property
def ERROR_TYPES(self):
return {
'Error': self.ErrorPrototype,
'EvalError': self.EvalErrorPrototype,
'RangeError': self.RangeErrorPrototype,
'ReferenceError': self.ReferenceErrorPrototype,
'SyntaxError': self.SyntaxErrorPrototype,
'TypeError': self.TypeErrorPrototype,
'URIError': self.URIErrorPrototype,
}
def get_global_environment(self):
return self.GlobalCtx.variable_environment()
def NewObject(self):
return PyJsObject(self.ObjectPrototype)
def NewFunction(self, code, ctx, params, name, is_declaration,
definitions):
return PyJsFunction(
code,
ctx,
params,
name,
self,
is_declaration,
definitions,
prototype=self.FunctionPrototype)
def NewDate(self, value):
return PyJsDate(value, self.DatePrototype)
def NewArray(self, length=0):
return PyJsArray(length, self.ArrayPrototype)
def NewError(self, typ, message):
return PyJsError(message, self.ERROR_TYPES[typ])
def NewRegExp(self, body, flags):
return PyJsRegExp(body, flags, self.RegExpPrototype)
def ConstructArray(self, py_arr):
''' note py_arr elems are NOT converted to PyJs types!'''
arr = self.NewArray(len(py_arr))
arr._init(py_arr)
return arr
def ConstructObject(self, py_obj):
''' note py_obj items are NOT converted to PyJs types! '''
obj = self.NewObject()
for k, v in py_obj.items():
obj.put(unicode(k), v)
return obj
@@ -0,0 +1,62 @@
from timeit import timeit
from collections import namedtuple
from array import array
from itertools import izip
from collections import deque
class Y(object):
UUU = 88
def __init__(self, x):
self.x = x
def s(self, x):
return self.x + 1
class X(Y):
A = 10
B = 2
C = 4
D = 9
def __init__(self, x):
self.x = x
self.stack = []
self.par = super(X, self)
def s(self, x):
pass
def __add__(self, other):
return self.x + other.x
def another(self):
return Y.s(self, 1)
def yet_another(self):
return self.par.s(1)
def add(a, b):
return a.x + b.x
t = []
Type = None
try:
print timeit(
"""
t.append(4)
t.pop()
""",
"from __main__ import X,Y,namedtuple,array,t,add,Type, izip",
number=1000000)
except:
raise

Some files were not shown because too many files have changed in this diff Show More