Compare commits

..
Author SHA1 Message Date
throwaway 2218fd6f04 change variable 2026-11-01 00:27:32 -07:00
lolcat be91ec1806 fix google news 2026-10-31 14:11:20 -04:00
lolcat 57a72e5b5e url redirect bypass 2026-10-31 14:01:52 -04:00
lolcat 9f0437d949 rotate ips on url resolution 2026-10-30 00:18:06 -04:00
lolcat d91834f86f fix filter bugs on google scraper 2026-10-29 23:56:12 -04:00
lolcat 42d0252e2f handle failed redirect fetches 2026-10-28 22:59:45 -04:00
lolcat a27a8ed01a fix potential xss with scraper supplied data in table, ty cynic 2026-10-27 01:12:32 -04:00
lolcat 9db95c79d2 update example apache2 config 2026-10-24 05:24:27 -04:00
lolcat 25d4f991c2 fix some php warnings 2026-10-24 04:03:00 -04:00
lolcat e1d0ab121f fix google cse crash 2026-10-24 03:24:52 -04:00
lolcat 33ce0a062b Merge branch 'master' of https://git.lolcat.ca/lolcat/4get 2026-10-24 03:19:13 -04:00
lolcat 3b3c759ce5 hyung..sunbae.. i fixed naver 2026-10-24 03:18:07 -04:00
lolcat 7eb4ac14c8 Merge pull request 'docker update php version' (#98) from docker_php_update into master
Reviewed-on: #98
2026-10-21 19:06:06 -04:00
throwaway 6c882ae245 update php 2026-10-21 13:01:51 -07:00
lolcat ee0cecc32b fix broken thumbnails, again 2026-10-18 22:40:01 -04:00
lolcat f1a64fc539 fix scrape failure when using quotes on brave 2026-10-18 22:28:02 -04:00
lolcat 77d636a44b ah i hate being retarded 2026-10-18 01:01:42 -04:00
lolcat fcf9f7a0b9 fix edge case on ddg wtf is this shit 2026-10-18 01:00:56 -04:00
lolcat e4ce351b37 block crawlers 2026-10-18 00:22:42 -04:00
lolcat fba304fb8c fix gen config 2026-10-16 01:08:59 -04:00
lolcat 5419d8afa5 google softcaptcha fix, again 2026-10-15 01:16:06 -04:00
lolcat 4e89d282cc fix broken thumbs on brave 2026-10-14 22:34:27 -04:00
lolcat 90eccc386c fix duckduckgo infinite pagination bug xd rawr 2026-10-13 01:16:11 -04:00
lolcat a837c0c248 fix bug with forwarded ip not being passed 2026-10-11 20:41:43 -04:00
lolcat 6602df4fa9 remove oracles, sorry cynic 2026-10-11 17:34:16 -04:00
lolcat 836521b8d7 dont ratelimit tor users 2026-10-10 18:19:14 -04:00
lolcat ca1c804eba disable ratelimiting by default 2026-10-10 17:30:07 -04:00
lolcat bfca82f4e3 fix bugs with bot request counter bleeeeeeerh 2026-10-10 17:22:49 -04:00
lolcat 8e0d2ca264 woops 2026-10-10 17:17:51 -04:00
lolcat 9135f9395c added ratelimit settings 2026-10-10 17:16:38 -04:00
lolcat 03ba5d7b5e i fixed the cock 2026-10-03 14:36:04 -04:00
lolcat 90e0a32366 why does google do this 2026-10-03 02:38:47 -04:00
lolcat cc8da092d2 fix softcaptcha being a little shit 2026-10-03 02:26:54 -04:00
lolcat 613f20b99f fix brave images, videos, news 2026-10-03 01:46:32 -04:00
lolcat 9ae7e3d7a3 brave fix 2026-10-03 01:41:57 -04:00
lolcat 047e400c7f fix 500px 2026-09-22 22:06:44 -04:00
lolcat 47b4788809 added bluesky account 2026-09-19 20:23:57 -04:00
lolcat a847ffec11 stats 2026-09-18 02:24:22 -04:00
lolcat f2150f3335 update donation information 2026-09-18 02:21:19 -04:00
lolcat 59b2ef8f0f ehh idk if you can get multiple lets just handle it that way for now 2026-09-15 00:33:55 -04:00
lolcat 0a40d39a52 detect legal complaints 2026-09-15 00:32:44 -04:00
lolcat b4c5f52cd3 robots 2026-09-11 23:15:21 -04:00
lolcat 734926240d google wagies!! yellow alert!! the 4get guy patched it again, do something!! 2026-09-08 20:23:35 -04:00
lolcat 017c4ba1b0 nice try google 2026-09-08 18:28:43 -04:00
lolcat 31a9ef4e13 remember what i said about sleeping 2026-09-08 04:37:40 -04:00
lolcat a210f77c99 dude i really need to fuck off to sleep 2026-09-08 04:35:16 -04:00
lolcat bdc3cffc7b i dont think this piece of shit website couldve came up with a more annoying url scheme 2026-09-08 04:29:34 -04:00
lolcat b328530951 murder 2026-09-08 04:27:19 -04:00
lolcat b37802e8f1 reddit pisses me off 2026-09-08 04:23:47 -04:00
lolcat 13c335dbdb hehe 2026-09-08 04:06:45 -04:00
lolcat e2e76fd007 false positive with twitch 2026-09-08 03:04:54 -04:00
lolcat c0823c84f3 fix typo 2026-09-08 02:53:41 -04:00
lolcat 311c99f298 detect yt captcha, added a shit ton of archive shortcuts to hidden menu 2026-09-08 02:47:20 -04:00
lolcat c3b450eed9 im retarded 2026-09-07 21:50:03 -04:00
lolcat bd04cf25d7 throwie the perfectionist 2026-09-07 21:49:04 -04:00
lolcat eaf66511f5 # 2026-09-07 21:45:47 -04:00
lolcat 9f7f294f39 formatting 2026-09-07 21:45:12 -04:00
lolcat 7d391fe3ef i said commit all the shit you piece of git 2026-09-07 21:44:20 -04:00
lolcat 01cea014d7 throwie's docker debug instructions + more shit 2026-09-07 21:43:50 -04:00
throwaway 67a15fa8e5 add missing font 2026-09-07 18:25:39 -07:00
lolcat 1f394883bf added debugging feature, fix graph misreporting request ranges in header 2026-09-06 23:51:51 -04:00
lolcat 1863dfa57b fix youtube dates. i deserve a fucking cup of tea 2026-09-06 15:10:34 -04:00
lolcat 462c5e1729 subdivide graph hours im tired of being in front of the computer 2026-09-05 21:05:41 -04:00
lolcat bfebf441b8 british deftones be like go get yor noife go get yor noife 2026-09-05 20:13:37 -04:00
lolcat 2305bd37f6 do you think a hot girl is reading this 2026-09-05 20:06:44 -04:00
lolcat 1a4b91435a request count is fucked, still need to fix graph pissing me the fuck off 2026-09-05 19:47:36 -04:00
lolcat be3abbef16 fix request count not resetting, added request graph, im so tired 2026-09-05 19:02:06 -04:00
lolcat 90232152e9 scrape google sublinks, which are encrypted pieces of shit 2026-09-02 01:41:55 -04:00
lolcat 06ee6878ca deny access to resolve 2026-09-01 18:39:24 -04:00
lolcat 28ba8422b2 dumbass moment 2026-09-01 18:29:56 -04:00
lolcat d598a560ae fix dmca links not showing up on google 2026-09-01 18:28:37 -04:00
lolcat 76c4b62d1a handle youtube age gate 2026-09-01 01:31:51 -04:00
lolcat a285a50e3b fix yt blurry thumbnails 2026-08-30 01:33:30 -04:00
lolcat e789e7ef04 table generator for readme, fix broken autocomplete 2026-08-30 00:23:15 -04:00
lolcat 0e01969075 well i guess you guys are gonna need to deal with blurry thumbnails cause maxresdefault is not always available either and black bars sucks even more so fuck this 2026-08-29 22:57:19 -04:00
lolcat f95485a17a apparently mqdefault sucks too, gotta pull the highres thumbnail fix your shit google 2026-08-29 22:49:22 -04:00
lolcat 0cc5a85399 use mqdefault cause the other endpoint cucks me with blurry thumbnails 2026-08-29 22:37:45 -04:00
lolcat c7165e0658 youtube scraper rewrite 2026-08-29 22:25:10 -04:00
lolcat 2d8e5838cd turn off history in ff 2026-08-28 20:56:58 -04:00
lolcat d7bb0755cb remove tracking links on video search using euroshit proxies 2026-08-27 19:05:18 -04:00
lolcat 89051f0129 remove cara 2026-08-27 18:31:35 -04:00
lolcat b2ab422f62 xi changed the description format 2026-08-25 00:10:45 -04:00
lolcat 2785f23a54 lmao forgot that little shit 2026-08-23 19:50:57 -04:00
lolcat 74665221c0 detect brave rangebans 2026-08-22 13:47:19 -04:00
lolcat 89b8be5147 detect brave captcha 2026-08-18 06:55:01 -04:00
lolcat 51b45ba53b add degoog themes, ty fccview, now you fags can stop complaining about 4get not having a modern theme 2026-08-14 20:34:45 -04:00
lolcat 1af8ad3a2b qwant doesnt return results past the first page, fuck this stupid shit 2026-08-14 20:18:23 -04:00
lolcat 01d4638687 maybe i should push the anubis code first you fucking moron 2026-08-14 19:54:26 -04:00
lolcat bd3f5923fa pass startpage js challenge, fix video pagination 2026-08-14 19:47:59 -04:00
lolcat ba7ee6e477 fallback to mojeek api for now 2026-08-09 02:45:05 -04:00
lolcat d39b8ab875 bypass google softcaptcha for real this time fuuuuuuuuuuuuuuuuuuuuuuuuuck 2026-08-06 01:48:50 -04:00
lolcat 0b78c99b5b holy shit why do i keep forgetting this 2026-08-06 01:22:30 -04:00
lolcat 53e315787c bypass google soft captcha 2026-08-06 01:21:27 -04:00
lolcat 4ca48d0b61 scraping google is my passion 2026-08-06 00:57:01 -04:00
lolcat 8db3304552 fuck my stupid chud life 2026-08-06 00:55:04 -04:00
lolcat 8e2aafddf8 detect google softcaptcha 2026-08-06 00:48:51 -04:00
lolcat 60014ebe4d add autistic graph 2026-08-05 03:06:14 -04:00
lolcat 538a3f6aa9 fix brave being a bitch on video and news 2026-08-05 01:51:34 -04:00
lolcat 1aa6278853 fix brave being a little shit about pagination 2026-08-05 01:48:10 -04:00
lolcat fdd1b4ab40 clarify ports used on 4play tehehe 2026-08-04 18:32:21 -04:00
lolcat 4de2712567 systemctl ffffffffuuaaaaaaakkkkk 2026-08-03 19:21:32 -04:00
lolcat 2d12a9b284 added 4play 4get config options lol 2026-08-03 19:15:54 -04:00
lolcat cd31e49785 better documentation for 4play 2026-08-03 19:06:13 -04:00
lolcat f6a369ed48 fix annoying bug with regex matching null like a fucking dumbass 2026-08-03 15:33:29 -04:00
lolcat e830a8f372 fix failstates not triggering properly 2026-08-03 15:20:09 -04:00
lolcat 919714d472 i hate change but i promised i would fix it 2026-08-02 17:15:51 -04:00
lolcat 9a4763ed3c fix proxy function 2026-08-01 23:01:12 -04:00
lolcat db90a39c4f google scraper fix 2026-08-01 20:04:04 -04:00
lolcat 8e7fe83774 my life wouldnt be complete without forgetting to commit settings.php 2026-07-25 20:18:06 -04:00
lolcat a43b3e3949 add purili scraper 2026-07-25 20:15:27 -04:00
lolcat e57538a296 mojeek captcha detect 2026-07-22 21:14:07 -04:00
lolcat 5fc80a3672 google cse pagination fix, thanks nikolay 2026-07-22 02:02:54 -04:00
lolcat 54d420f97c yep fix. fuck fsky 2026-07-15 02:15:43 -04:00
lolcat 513bb666c5 wordnik formatting fix 2026-07-15 01:50:28 -04:00
lolcat 3e4c7c1626 forgot to fix qwant images lllllooooooollllllll 2026-07-10 21:49:25 -04:00
lolcat e4c7250cad qwant fix, datadome is such a joke lol 2026-07-10 21:42:10 -04:00
lolcat c1e9d52775 notes for me 2026-07-05 18:40:43 -04:00
lolcat 46411a6c83 i overengineered the fuck out of that baidu scraper jesus 2026-07-05 01:57:56 -04:00
lolcat 8328d93b17 duckduckgo fix 2026-07-04 17:16:35 -04:00
NECOtype abdb041a64 add Rosé Pine theme 2026-06-27 13:25:50 -04:00
lolcat f037ad1d7f serverping inlineJS fix (ty ethan) 2026-06-25 23:20:06 -04:00
lolcat 347300cc26 for some reason mojeek doesnt want me using region settings anymore lol 2026-06-21 22:15:27 -04:00
lolcat 5a7cecef11 marginalia api 502 2026-06-14 16:07:33 -04:00
lolcat d87bac94b5 handle marginalia internal error 2026-06-14 16:01:56 -04:00
lolcat d54e4dfcdd qwant fix 2026-06-14 15:10:55 -04:00
lolcat b28767d8f0 fix ACL rules for apache2 2026-06-08 01:06:51 -04:00
lolcat 7d8b12ba9d cleaning up useless shit 2026-06-08 00:48:34 -04:00
lolcat a430ef5a12 remove ghostery search, discontinued 2026-06-06 04:14:50 -04:00
lolcat 02cab7632d license clarification 2026-06-06 02:18:30 -04:00
lolcat 76f7d0de19 wtf it works for yep too 2026-06-05 00:22:24 -04:00
lolcat 7ffbc5f67b why does http1 work jej 2026-06-05 00:18:55 -04:00
lolcat 50349d2390 why do you fucking do that naver 2026-06-01 02:53:48 -04:00
lolcat fc8b41bfad added naver kansanmidaaaa 2026-06-01 02:48:27 -04:00
lolcat caa9a6c516 yep api fix 2026-05-31 20:15:09 -04:00
lolcat 572730415a startpage pagination fix, fucking again 2026-05-31 13:42:50 -04:00
lolcat 72fc1ae080 stop changing the fucking encoding of the motherfucking word corrector 2026-05-29 21:54:16 -04:00
lolcat ba2c6f6bb1 fix startpage pagination, again 2026-05-29 21:48:06 -04:00
lolcat d7d35f02f3 holy shit im retarded 2026-05-27 21:55:56 -04:00
lolcat 5f112737b8 detect ddg captcha 2026-05-27 21:54:36 -04:00
lolcat 1e3f3d82fd remove baseless degoog slander lolololo 2026-05-24 22:59:20 -04:00
lolcat 95819bfe52 yep api fix 2026-05-20 11:05:41 -04:00
lolcat e1e92d715e add support for yep api 2026-05-20 11:01:18 -04:00
lolcat 394f401921 Merge pull request 'add lwthiker/curl-impersonate' (#94) from docker-curl-impersonate into master
Reviewed-on: #94
2026-05-20 05:02:30 +00:00
lolcat 25e8095d0d yep scraper fix? still getting tons of CF errors... 2026-05-20 00:59:27 -04:00
throwaway cf3c77ed04 add lwthiker/curl-impersonate 2026-05-19 17:08:10 -07:00
lolcat c45f8b1e12 fix startpage pagination and fuckups with word corrections 2026-05-09 22:24:36 -04:00
lolcat 6086c63148 extract_json fix 2026-05-05 03:32:45 -04:00
lolcat d2b0a414ad yandex videos fix 2026-05-02 17:44:53 -04:00
lolcat c713d52b5f google scraper fix.. haha just kidding i modified the readme file 2026-05-01 20:10:51 -04:00
lolcat 0861450b8a detect pinterest captcha 2026-04-29 01:29:54 -04:00
lolcat 88012f6ae2 i hate git 2026-04-29 01:20:04 -04:00
lolcat 0dabcea0aa link fix 2026-04-29 01:16:50 -04:00
lolcat a8022d22a7 remove settings 2026-04-26 16:24:29 -04:00
lolcat 9ea0372bb7 remove extremely low quality scrapers 2026-04-26 16:22:24 -04:00
lolcat a54f212550 yep scraper fix, removed image and news 2026-04-26 05:08:15 -04:00
lolcat b1f5974e40 new google message 2026-04-25 23:03:42 -04:00
lolcat e63a17d6db added google api image scraper 2026-04-25 22:58:28 -04:00
lolcat 4349bf232d captcha bruteforce fix 2026-04-11 17:32:20 -04:00
lolcat 60d6f649ee Merge pull request 'add evergarden theme' (#90) from koi/4get:evergarden into master
Reviewed-on: #90
2026-04-07 15:38:23 +00:00
koi 7fc6244738 add evergarden theme 2026-04-07 13:01:52 +02:00
lolcat 70f00aa55f added pexels and unsplash woooohoooooooo 2026-04-06 21:02:54 -04:00
lolcat 5a351e0dfc forgot config, dementia moment 2026-04-05 23:58:15 -04:00
lolcat 429e53d41f forgot settings 2026-04-05 23:45:11 -04:00
lolcat 8954d1aea5 added pixabay 2026-04-05 23:44:26 -04:00
lolcat fdd44dd373 detect qwant captcha 2026-03-29 15:37:05 -04:00
lolcat 2386dd707e pagination fix 2026-03-18 01:20:10 -04:00
lolcat 61548e8b84 fix google images 2026-03-18 00:01:22 -04:00
lolcat 2e5edda85b fix scrape failures on google 2026-03-05 21:06:19 -05:00
lolcat 4e247c3ac4 google is now usable again 2026-03-04 03:08:33 -05:00
lolcat f324ddd3a7 the excuse of a search engine broke my scraper again. go back to generating slop you waste of oxygen 2026-03-04 02:07:43 -05:00
lolcat cdf9164113 stop fucking changing how you redirect people you piece of shit 2026-02-24 17:53:38 -05:00
lolcat c42e62cb09 fix marginalia failing 2026-02-24 00:35:21 -05:00
lolcat afc7318cf7 the fuck? isset returns false if the variable is set but null. what the fuck lol 2026-01-31 02:38:33 -05:00
lolcat 1991206de4 fix soundcloud token, again 2026-01-25 03:44:48 -05:00
lolcat 92eb6ab14d apache2 doesnt like folders with same name as script 2025-12-14 17:20:05 -05:00
lolcat 4f587e32e1 unfuck yourself 2025-12-14 17:18:58 -05:00
lolcat 0243e4edf2 bypass soundclouds recent bullshit 2025-12-14 17:03:14 -05:00
lolcat fd155b192e cloudfront ratelimit handler 2025-12-13 20:03:23 -05:00
lolcat 800cde93cf soundcloud token expiry fix 2025-12-13 19:41:47 -05:00
lolcat aa7c85b98a forgot the fucking settings 2025-12-01 02:39:40 -05:00
lolcat 4f30a3ac39 added yahoo japan, still missing the search filters though 2025-12-01 02:37:07 -05:00
lolcat bbfbe59ffb fuck off yahoo 2025-11-28 20:51:11 -05:00
lolcat a110b78a72 remove mullvad 2025-11-28 20:50:28 -05:00
lolcat 46e6ed12e3 fix invalid sublinks on google scraper 2025-10-21 00:35:20 -04:00
lolcat ce75cbda81 google engineers on suicide watch 2025-10-19 03:56:37 -04:00
lolcat 560b9b04da handle google api no results 2025-10-10 00:15:39 -04:00
lolcat 56ea4811d7 woops 2025-10-08 22:18:03 -04:00
lolcat 036a097d4d fix offset on next page 2025-10-08 01:32:47 -04:00
lolcat a4a44709b4 google quote on quote fix 2025-10-08 00:42:36 -04:00
lolcat 4b16fd5897 filter ads from the html endpoint 2025-09-30 19:56:30 -04:00
lolcat 61deefb75b fucking operators are blocked too 2025-09-28 15:27:07 -04:00
lolcat 8198287ec0 fallback to html endpoint when user searches with quotes 2025-09-28 15:23:57 -04:00
lolcat bf6319839e chrome ua does nothing, nevermind 2025-09-28 13:18:06 -04:00
lolcat fa4aa9a0fd bypass ddg challenge 2025-09-28 13:14:11 -04:00
lolcat c69abf41b0 google scraper fix 2025-09-11 02:05:21 -04:00
lolcat 1b6182bc3c now scraping image resolutions on brave 2025-09-10 01:09:48 -04:00
lolcat 1cfeabeb7f forgot the fucking frontend duhhhhh 2025-09-07 14:32:28 -04:00
lolcat 73f8472eec re-added solofield, added mullvad for brave and google 2025-09-07 14:26:51 -04:00
lolcat 0c90c4bc9e remove deep_simple_no_results nag 2025-09-06 16:24:02 -04:00
lolcat cfcc4ec8d1 please stop fucking up everything you piece of shit extract_json function 2025-09-06 16:11:51 -04:00
lolcat 6d34d43a01 fixed greppr, again 2025-09-06 11:25:09 -04:00
lolcat c44d6292a0 holy shit can i stop fucking up this function for fucks sake 2025-09-06 09:44:05 -04:00
lolcat 0b350d4d6e messed up json extractor again 2025-09-06 01:50:20 -04:00
lolcat de328fff1b messed up json extractor 2025-09-06 01:49:22 -04:00
lolcat 8613c1e0f4 brave crash fix 2025-09-05 01:59:01 -04:00
lolcat d4aaebcd80 remove that thing 2025-08-30 23:41:43 -04:00
lolcat a2e056b47b workaround to get bigger wiki images 2025-08-30 23:32:01 -04:00
lolcat 8a0a8359a8 handle captcha on coc coc 2025-08-30 19:21:19 -04:00
lolcat dcf5901809 fix warning in goolag scraper 2025-08-24 11:22:43 -04:00
lolcat aa9806300a doc changes 2025-08-19 23:45:05 -04:00
lolcat 91ce5c1563 remove ssl ciphers they bug out the new method 2025-08-19 23:28:54 -04:00
lolcat cdf958d293 fix wikipedia crash 2025-08-10 21:55:15 -04:00
lolcat 2d63475b07 fix MDN answers not rendering properly 2025-08-10 21:49:51 -04:00
lolcat ae31274db9 these comments were too long 2025-08-10 21:39:19 -04:00
lolcat 20ef5b3e3a re-added stackoverflow instant answers 2025-08-10 21:37:45 -04:00
lolcat 7c970031d0 fix #2 for real this time 2025-08-10 17:22:58 -04:00
lolcat 2c2bd28a9f fix syntax highlighter 2025-08-10 17:15:42 -04:00
lolcat dea8b0a362 this was so much pain to figure out 2025-08-10 16:39:11 -04:00
lolcat da1ea1d6e8 add more hacks 2025-08-10 15:53:02 -04:00
lolcat 2ca8fb0006 remove backtick 2025-08-10 15:44:46 -04:00
lolcat 362cf61508 doc config changes number twoo 2025-08-10 15:43:49 -04:00
lolcat 1828c63233 doc config changes 2025-08-10 15:42:14 -04:00
lolcat 4215f2678d i always forget the fucking config 2025-08-09 13:34:45 -04:00
lolcat acd02d83d4 added cara.app 2025-08-09 13:28:36 -04:00
lolcat 319640cd77 greppr fix 2025-08-09 11:00:48 -04:00
lolcat ad535a1609 fix broken mojeek instant answer iamge 2025-08-07 17:56:39 -04:00
lolcat 7ac53c6e11 bypass mojeek bot check 2025-08-07 17:49:45 -04:00
lolcat f33f02e816 so fucking dumb 2025-08-06 21:10:58 -04:00
lolcat 27b8509ac0 fix ddg answer image 404 2025-08-06 21:09:33 -04:00
lolcat 1e52982cb9 fix ddg returning weird reresults when no match is found 2025-08-06 20:19:13 -04:00
lolcat 3f2bfcb8c7 aaaaaa part 3 2025-08-04 00:39:47 -04:00
lolcat d3ef1a67c0 aaaaaa part 2 2025-08-04 00:39:13 -04:00
lolcat 9afef55d89 aaaaaa 2025-08-04 00:37:12 -04:00
lolcat 706b490bf3 bypass captcha??? hopefullly........ 2025-08-04 00:34:06 -04:00
lolcat 74f7c920f6 handle vimeo captchas 2025-08-04 00:18:38 -04:00
lolcat 3dbcf60a3e added vimeo 2025-08-04 00:00:39 -04:00
lolcat f30872134f handle mojeek block 2025-08-03 12:28:57 -04:00
lolcat 2c4dc7da84 lol forgot the request is still for yt 2025-08-02 17:44:10 -04:00
lolcat 5a0f5b868a i hate git 2025-08-02 16:48:58 -04:00
lolcat 7cf403e125 forgot the fucking config 2025-08-02 16:39:34 -04:00
lolcat 73b7922898 added sepia search 2025-08-02 16:38:36 -04:00
lolcat 336cb49d98 im so retarded 2025-08-01 20:54:08 -04:00
lolcat 8cd8e7380f readme 2025-07-30 23:21:12 -04:00
lolcat 3d9d95db34 ffffffffffukccccccccc 2025-07-30 15:49:05 -04:00
lolcat eb73b1f357 dementia 2025-07-30 15:47:29 -04:00
lolcat f7499294de added cock cock, removed solofield 2025-07-30 15:35:27 -04:00
lolcat 60f7150008 readme 2025-07-28 18:53:21 -04:00
lolcat 2b8d90af12 forgot the config fucking dementiacatwill 2025-07-27 21:53:20 -04:00
lolcat 0f803804a4 forgot the settings damn it 2025-07-27 21:48:10 -04:00
lolcat f43feff0aa added baidu, the best search engine 2025-07-27 21:46:03 -04:00
lolcat 0bdd5e73df added kuuro theme 2025-07-27 11:52:09 -04:00
lolcat 430c0a2f0f fix potential xss woops 2025-07-08 23:10:13 -04:00
lolcat 1a00bf8069 duckduckgo spelling fix 2025-07-08 23:08:12 -04:00
lolcat 502f6d12e4 marginalia crash fix 2025-07-03 19:43:58 -04:00
lolcat a2bc1e6190 bypass anubis bullshit on marginalia 2025-06-20 01:18:57 -04:00
lolcat f73b5f0298 fix yandex web 2025-06-18 10:30:31 -04:00
lolcat 3e1487e614 maybe we should call the function chucknuts 2025-06-12 10:30:47 -04:00
lolcat 037566bbba handle google captcha 2025-06-11 18:44:41 -04:00
lolcat b61bc6d07c fix google image crash 2025-06-01 13:03:39 -04:00
lolcat 8d50667b0d handle imgur ip block 2025-05-27 20:03:40 -04:00
lolcat a0545b6006 fix startpage videos 2025-05-24 20:49:49 -04:00
lolcat 78aa2e198f fuiwhwehfuiewuf 2025-04-19 10:42:21 -04:00
lolcat b85820cbcd hopefully this fixes bing images my fucking god 2025-04-19 10:37:35 -04:00
lolcat 4b85841a3e forgot nsfw filter god damn 2025-04-17 21:41:12 -04:00
lolcat 8d07e72dfe forgot settings, god damn i have dementia 2025-04-17 20:08:15 -04:00
lolcat 566680fe36 ok its unfucked now 2025-04-17 20:06:42 -04:00
lolcat 077692db49 i fucking hate bing 2025-04-17 20:05:58 -04:00
lolcat e4bf53cdaa forgot config 2025-04-17 20:02:02 -04:00
lolcat 4489bb21e5 forgot flickr 2025-04-17 20:00:33 -04:00
lolcat ff8b1addf7 fixed bing images failing to load, added flickr 2025-04-17 19:54:34 -04:00
lolcat 3e2c3fc5d9 fixed google videos 2025-04-02 21:40:53 -04:00
lolcat 49ddd1a216 duckduckgo images nsfw fix 2025-03-20 21:05:36 -04:00
lolcat 81ca8eaddc gore theme fix 2025-03-16 03:39:58 -04:00
lolcat c9c8d578f3 Merge branch 'master' of https://git.lolcat.ca/lolcat/4get 2025-03-02 21:58:34 -05:00
lolcat b2203804c7 path traversal exploit (this is what you get for using free software) 2025-03-02 21:58:18 -05:00
lolcat 13dfa9240c Merge pull request 'fix Dockerfile build.' (#67) from Fijxu/4get:dockerfile-fix into master
Reviewed-on: #67
2025-02-04 07:05:12 +00:00
Fijxu 0a53c3605a fix Dockerfile build.
The `alpine:latest` image do not longer include php83 on their repos.
Using a specific image tag is better to prevent breakages on the future.

Ref: https://github.com/dnaprawa/dockerfile-best-practices?tab=readme-ov-file#the-latest-is-an-evil-choose-specific-image-tag
2025-02-02 01:37:14 -03:00
lolcat 36b0c570aa Merge branch 'master' of https://git.lolcat.ca/lolcat/4get 2025-02-01 22:53:06 -05:00
lolcat 47a7a2a224 brave pagination fix 2025-02-01 22:51:44 -05:00
lolcat 0180cf5224 Merge pull request 'override ssl.conf when using http config' (#63) from docker_fix_conf into master
Reviewed-on: #63
2025-01-24 15:41:41 +00:00
throwaway eed32a153c remove ssl.conf when using http config 2025-01-24 00:53:34 -08:00
lolcat f9f3c919d6 brave scraper fix 2025-01-22 20:04:42 -05:00
lolcat 4b0d8f75dc google scraper fix 2025-01-19 14:02:24 -05:00
lolcat 033e4cb959 added vsco scraper 2025-01-11 23:07:58 -05:00
lolcat 91f621e105 readme changes 2025-01-11 14:34:23 -05:00
lolcat 9f60900875 500px scraper 2025-01-11 14:12:54 -05:00
lolcat 631aa58565 marginalia hotfix 2025-01-07 21:12:07 -05:00
lolcat b892f90b13 readme changes 2025-01-07 00:05:42 -05:00
lolcat 463ba0775f added pinterest 2025-01-06 23:56:49 -05:00
lolcat cfad4fb035 wordnik bugfix 2025-01-06 21:05:45 -05:00
lolcat 4e968b4b1c youtube scraper fix 2025-01-03 22:22:30 -05:00
lolcat 81df52235c fixed google crash 2025-01-03 21:43:40 -05:00
lolcat 1ca2626ad9 fix ddg bug with EOF result 2025-01-03 21:16:00 -05:00
lolcat 9ca93f34c6 ddg hotfix 2024-12-17 21:01:36 -05:00
lolcat 0a43b9c849 added arquivo.pt 2024-12-17 10:11:53 -05:00
lolcat b636fec319 fucking git is so shit 2024-12-17 00:35:15 -05:00
lolcat 774f7113df duckduckgo scraper rewrite 2024-12-17 00:31:15 -05:00
lolcat 0b3bbe0f15 gore's shitty theme fix 2024-12-02 15:19:10 -05:00
lolcat 5f0b0a7b83 findthatmeme fix 2024-12-01 15:59:03 -05:00
lolcat 920b9d5b3f brave crash fix 2024-11-19 09:22:58 -05:00
lolcat 9cd369ac08 http2 on ddg 2024-11-07 23:37:43 -05:00
lolcat e83865be49 added pagination 2024-11-07 00:12:06 -05:00
lolcat 68dd7f29f6 mojeek thumbnail fix 2024-11-06 23:43:54 -05:00
lolcat aaa30c79f5 fix google cse image crash + added word autocorrect 2024-10-31 20:31:23 -04:00
lolcat 070f9d442b brave fix 2024-10-29 21:29:17 -04:00
lolcat 9c18753ec3 yes of course i need to fucking forget the .php again AAAAAAAAAAA 2024-10-24 21:46:54 -04:00
lolcat d8a729796e fix crash on google cse, added settings 2024-10-22 20:15:00 -04:00
lolcat 2bbe5a29a9 Merge branch 'master' of https://git.lolcat.ca/lolcat/4get 2024-10-22 11:34:06 -04:00
lolcat 9ac195ac3b added google CSE 2024-10-22 11:33:14 -04:00
lolcat d427a48ed4 Merge pull request 'nginx documentation but better' (#41) from bread/4get:master into master
Reviewed-on: #41
2024-10-21 14:17:07 +00:00
lolcat 12d5b4ade8 Merge branch 'master' into master 2024-10-21 14:16:54 +00:00
bread c422abbdc6 add css via copy and paste (slightly edited to not require a lot from my shithole) 2024-10-21 14:15:34 +00:00
bread 85246cc7ec 194 lines of mark(down) 2024-10-19 01:12:41 +00:00
bread d709d12111 fix html making md look bad 2024-10-10 04:45:06 +00:00
bread 19f82a8536 even more little things I missed (polish!) 2024-10-08 19:22:38 +00:00
bread 155a38d454 things I missed 2024-10-08 19:19:01 +00:00
bread 6926e374af Upgrade nginx configuration to a better state 2024-10-07 23:48:24 +00:00
103 changed files with 24166 additions and 11904 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
.git
+13 -4
View File
@@ -1,10 +1,18 @@
FROM alpine:latest FROM lwthiker/curl-impersonate:0.6.1-ff-alpine AS curl-impersonate
FROM alpine:3.24
WORKDIR /var/www/html/4get WORKDIR /var/www/html/4get
RUN apk update && apk upgrade RUN apk update && apk upgrade
RUN apk add php apache2-ssl php83-fileinfo php83-openssl php83-iconv php83-common php83-dom php83-sodium php83-curl curl php83-pecl-apcu php83-apache2 imagemagick php83-pecl-imagick php-mbstring imagemagick-webp imagemagick-jpeg RUN apk add php apache2-ssl php85-fileinfo php85-openssl php85-iconv php85-common php85-dom php85-sodium php85-curl curl php85-pecl-apcu php85-apache2 imagemagick php85-pecl-imagick php85-mbstring imagemagick-webp imagemagick-jpeg nss ca-certificates ghostscript-fonts
COPY --from=curl-impersonate /usr/local/bin /usr/local/bin
COPY --from=curl-impersonate /usr/local/lib /usr/local/lib
ENV LD_PRELOAD=/usr/local/lib/libcurl-impersonate-ff.so
ENV CURL_IMPERSONATE=ff117
ENV CURL_IMPERSONATE_HEADERS=no
COPY ./docker/apache/ /etc/apache2/
COPY . . COPY . .
RUN chmod 777 /var/www/html/4get/icons RUN chmod 777 /var/www/html/4get/icons
@@ -14,4 +22,5 @@ EXPOSE 443
ENV FOURGET_PROTO=http ENV FOURGET_PROTO=http
CMD ["./docker/docker-entrypoint.sh"] ENTRYPOINT ["./docker/docker-entrypoint.sh"]
CMD ["start"]
+33 -27
View File
@@ -9,13 +9,15 @@ https://4get.ca/about
## Official instance ## Official instance
https://4get.ca , or visit the official instance list: https://4get.ca/instances https://4get.ca , or visit the official instance list: https://4get.ca/instances
_NOT to be confused with 4get.ch, 4get.lol and friends! I **don't** host these._
## Totally unbiased comparison between alternatives ## Totally unbiased comparison between alternatives
| | 4get | searx(ng) | libreY | araa | hearch | | | 4get | searx(ng) | whoogle | degoog |
|----------------------------|-------------------------|-----------|-------------|-----------|-------------------| |----------------------------|-------------------------|-----------|------------|-------------------------------------------|
| RAM usage | 200-400mb~ | 2GB~ | 200-400mb~ | 2GB~ | idk | | RAM usage | 100-400mb~ | 400mb-1GB | 100mb | 100mb-250mb |
| Does it suck | no (debunked by snopes) | yes | yes | a little | better than searx | | Does it suck | no (debunked by snopes) | yes | kind of? | hit and miss with search filters |
| Does it work | ye | sometimes | sometimes | sometimes | yes | | Does it work | ye | lmao | shits dead | works $rightNow, it's actually kinda cool |
## Features ## Features
1. Rotating proxies on a per-scraper basis 1. Rotating proxies on a per-scraper basis
@@ -23,36 +25,40 @@ https://4get.ca , or visit the official instance list: https://4get.ca/instances
3. Bot protection that *actually* filters out the bots (when configured) 3. Bot protection that *actually* filters out the bots (when configured)
4. Interface doesn't require javascript 4. Interface doesn't require javascript
5. Favicon fetcher with caching support & image proxy 5. Favicon fetcher with caching support & image proxy
6. Bunch of other shit 6. Bunch of other shits
tl;dr the best way to actually browse for shit. tl;dr 4get is the best way to browse for shit.
# Supported websites # Supported websites
| Web | Images | Videos | News | Music | Autocompleter | | web | images | videos | news | music | autocomplete |
|------------|--------------|------------|------------|------------|---------------| |--------------|--------------|--------------|--------------|------------|--------------|
| DuckDuckGo | DuckDuckGo | YouTube | DuckDuckGo | Soundcloud | Brave | | DuckDuckGo | DuckDuckGo | YouTube | DuckDuckGo | SoundCloud | Brave |
| Brave | Brave | DuckDuckGo | Brave | | DuckDuckGo | | Brave | Yandex | Vimeo | Brave | Swisscows | DuckDuckGo |
| Yandex | Yandex | Brave | Google | | Yandex | | Yandex | Brave | Sepia Search | Google | | Yandex |
| Google | Google | Yandex | Startpage | | Google | | Google | Google | DuckDuckGo | Yahoo! JAPAN | | Google |
| Startpage | Startpage | Google | Qwant | | Startpage | | Google API | Google API | Brave | Startpage | | Qwant |
| Qwant | Qwant | Startpage | Mojeek | | Kagi | | Google CSE | Google CSE | Yandex | Baidu | | Marginalia |
| Ghostery | Yep | Qwant | | | Qwant | | Yahoo! JAPAN | Yahoo! JAPAN | Google | | | YouTube |
| Yep | Solofield | Solofield | | | Ghostery | | Startpage | Startpage | Yahoo! JAPAN | | | SoundCloud |
| Greppr | Imgur | | | | Yep | | Yep | Naver | Startpage | | | Startpage |
| Crowdview | FindThatMeme | | | | Marginalia | | Mwmbl | Baidu | Naver | | | Kagi |
| Mwmbl | | | | | YouTube | | Mojeek | Solofield | Baidu | | | |
| Mojeek | | | | | Soundcloud | | Naver | Pinterest | Cốc Cốc | | | |
| Solofield | | | | | | | Baidu | Flickr | Purili | | | |
| Marginalia | | | | | | | Cốc Cốc | Pexels | Solofield | | | |
| wiby | | | | | | | Solofield | Pixabay | | | | |
| Curlie | | | | | | | Marginalia | Unsplash | | | | |
| Purili | 500px | | | | |
| wiby | VSCO | | | | |
| | Imgur | | | | |
| | FindThatMeme | | | | |
# Installation # Installation
Refer to the <a href="https://git.lolcat.ca/lolcat/4get/src/branch/master/docs/">documentation index</a>. I recommend following the <a href="https://git.lolcat.ca/lolcat/4get/src/branch/master/docs/apache2.md">apache2 guide</a>. Refer to the <a href="https://git.lolcat.ca/lolcat/4get/src/branch/master/docs/">documentation index</a>. I recommend following the <a href="https://git.lolcat.ca/lolcat/4get/src/branch/master/docs/apache2.md">apache2 guide</a>.
## Contact ## Contact
Shit breaks all the time but I repair it all the time too... Email me here: <b>will (at) lolcat.ca</b> or create an issue. Shit breaks all the time but I repair it all the time too. Email me here: <b>will (at) lolcat.ca</b> or create an issue.
## License ## License
AGPL AGPLv3-only
+17 -5
View File
@@ -5,8 +5,20 @@ header("Access-Control-Allow-Origin: *");
include "data/config.php"; include "data/config.php";
$real_requests = apcu_fetch("real_requests"); // get request count in last 24 hours
$bot_requests = apcu_fetch("captcha_gen"); $bot_requests = 0;
$real_requests = 0;
$time = time();
for($i=0; $i<24; $i++){
$bot_requests += apcu_fetch(intdiv($time - (3600 * $i), 3600) . ".bot_requests");
}
for($i=0; $i<24; $i++){
$real_requests += apcu_fetch(intdiv($time - (3600 * $i), 3600) . ".real_requests");
}
echo json_encode( echo json_encode(
[ [
@@ -16,10 +28,10 @@ echo json_encode(
"name" => config::SERVER_NAME, "name" => config::SERVER_NAME,
"description" => config::SERVER_LONG_DESCRIPTION, "description" => config::SERVER_LONG_DESCRIPTION,
"bot_protection" => config::BOT_PROTECTION, "bot_protection" => config::BOT_PROTECTION,
"real_requests" => $real_requests === false ? 0 : $real_requests, "real_requests" => $real_requests,
"bot_requests" => $bot_requests === false ? 0 : $bot_requests, "bot_requests" => $bot_requests,
"api_enabled" => config::API_ENABLED, "api_enabled" => config::API_ENABLED,
"alt_addresses" => config::ALT_ADDRESSES, "alt_addresses" => config::ADDRESSES,
"version" => config::VERSION "version" => config::VERSION
], ],
"instances" => config::INSTANCES "instances" => config::INSTANCES
+13 -6
View File
@@ -1,9 +1,16 @@
__ __ __ 44
/ // / ____ ____ / /_ 4444444 44
/ // /_/ __ `/ _ \/ __/ 44444444 44444 444
/__ __/ /_/ / __/ /_ 44444444 444444 444444444
/_/ \__, /\___/\__/ 44444 44444444 444444444
/____/ 444444444 4444444
4444444444 444444
4444444444444
444444444444444444
444444444444444
44444444
4444
44
+ Welcome to the 4get API documentation + + Welcome to the 4get API documentation +
+3 -3
View File
@@ -15,13 +15,11 @@ class autocomplete{
"yandex" => "https://suggest.yandex.com/suggest-ff.cgi?part={searchTerms}&uil=en&v=3&sn=5&lr=21276&yu=4861394161661655015", "yandex" => "https://suggest.yandex.com/suggest-ff.cgi?part={searchTerms}&uil=en&v=3&sn=5&lr=21276&yu=4861394161661655015",
"google" => "https://www.google.com/complete/search?client=mobile-gws-lite&q={searchTerms}", "google" => "https://www.google.com/complete/search?client=mobile-gws-lite&q={searchTerms}",
"qwant" => "https://api.qwant.com/v3/suggest/?q={searchTerms}&client=opensearch", "qwant" => "https://api.qwant.com/v3/suggest/?q={searchTerms}&client=opensearch",
"yep" => "https://api.yep.com/ac/?query={searchTerms}",
"marginalia" => "https://search.marginalia.nu/suggest/?partial={searchTerms}", "marginalia" => "https://search.marginalia.nu/suggest/?partial={searchTerms}",
"yt" => "https://suggestqueries-clients6.youtube.com/complete/search?client=youtube&q={searchTerms}", "yt" => "https://suggestqueries-clients6.youtube.com/complete/search?client=youtube&q={searchTerms}",
"sc" => "", "sc" => "",
"startpage" => "https://www.startpage.com/suggestions?q={searchTerms}&format=opensearch&segment=startpage.defaultffx&lui=english", "startpage" => "https://www.startpage.com/suggestions?q={searchTerms}&format=opensearch&segment=startpage.defaultffx&lui=english",
"kagi" => "https://kagi.com/api/autosuggest?q={searchTerms}", "kagi" => "https://kagi.com/api/autosuggest?q={searchTerms}"
"ghostery" => "https://ghosterysearch.com/suggest?q={searchTerms}"
]; ];
/* /*
@@ -165,6 +163,8 @@ class autocomplete{
// if it respects the openSearch protocol // if it respects the openSearch protocol
$json = json_decode($this->get($this->scrapers[$scraper], $_GET["s"]), true); $json = json_decode($this->get($this->scrapers[$scraper], $_GET["s"]), true);
if(!isset($json[1])){ $json[1] = []; }
echo json_encode( echo json_encode(
[ [
$_GET["s"], $_GET["s"],
+3
View File
@@ -27,6 +27,9 @@ new bot_protection($null, $null, $null, "images", false);
$get = $frontend->parsegetfilters($_GET, $filters); $get = $frontend->parsegetfilters($_GET, $filters);
// increment request count
$frontend->increment_real_reqs($get["scraper"]);
try{ try{
echo json_encode( echo json_encode(
$scraper->image($get), $scraper->image($get),
+3
View File
@@ -27,6 +27,9 @@ new bot_protection($null, $null, $null, "music", false);
$get = $frontend->parsegetfilters($_GET, $filters); $get = $frontend->parsegetfilters($_GET, $filters);
// increment request count
$frontend->increment_real_reqs($get["scraper"]);
try{ try{
echo json_encode( echo json_encode(
$scraper->music($get), $scraper->music($get),
+3
View File
@@ -27,6 +27,9 @@ new bot_protection($null, $null, $null, "news", false);
$get = $frontend->parsegetfilters($_GET, $filters); $get = $frontend->parsegetfilters($_GET, $filters);
// increment request count
$frontend->increment_real_reqs($get["scraper"]);
try{ try{
echo json_encode( echo json_encode(
$scraper->news($get), $scraper->news($get),
+3
View File
@@ -27,6 +27,9 @@ new bot_protection($null, $null, $null, "videos", false);
$get = $frontend->parsegetfilters($_GET, $filters); $get = $frontend->parsegetfilters($_GET, $filters);
// increment request count
$frontend->increment_real_reqs($get["scraper"]);
try{ try{
echo json_encode( echo json_encode(
$scraper->video($get), $scraper->video($get),
+3
View File
@@ -38,6 +38,9 @@ if(
$get["extendedsearch"] = "no"; $get["extendedsearch"] = "no";
} }
// increment request count
$frontend->increment_real_reqs($get["scraper"]);
try{ try{
echo echo
+100
View File
@@ -0,0 +1,100 @@
<?php
header("Content-Type: image/png");
date_default_timezone_set('America/Toronto');
include "../../data/config.php";
function get_requests($slug){
$time = time();
$requests = [];
for($i=0; $i<73; $i++){
$data = apcu_fetch(intdiv($time - (3600 * $i), 3600) . ".$slug");
$requests[] = [$i, ($data === false ? 0 : $data)];
}
// rtrim() out all entries that are 0
$out = [];
$found = false;
for($i=count($requests) - 1; $i>=0; $i--){
if($requests[$i][1] !== 0){
$found = $i;
break;
}
}
if($found === false){
return [];
}
$requests = array_slice($requests, 0, $found + 1);
return $requests;
}
$bot_requests = get_requests("bot_requests");
$real_requests = get_requests("real_requests");
$real_reqs_8h =
array_sum(
array_column(
array_slice(
$real_requests,
0,
8
),
1
)
);
$real_reqs_24h =
array_sum(
array_column(
array_slice(
$real_requests,
0,
24
),
1
)
);
$real_reqs_72h =
array_sum(
array_column(
$real_requests,
1
)
);
include "../../lib/graph_gen.php";
$graph = new graph();
$c = count($real_requests);
if($c > 25){
$c = 25;
}elseif($c <= 2){
$c = 2;
}
echo $graph->render([
"title" => "Number of requests within the last 72 hours (non-cummulative)",
"subtitle" => date("jS M y @ g:ia", time()) . " || 8h=" . number_format($real_reqs_8h) . " - 24h=" . number_format($real_reqs_24h) . " - 72h=" . number_format($real_reqs_72h) . " || " . config::SERVER_NAME,
"x_label" => "<= Hours passed =>",
"y_label" => "<= Request count/hour =>",
"grad_x" => $c,
"grad_y" => 20,
"data" => [
"Bot requests" => $bot_requests,
"Real requests" => $real_requests,
]
]);
-20
View File
@@ -1,20 +0,0 @@
<?php
if(!isset($_GET["s"])){
http_response_code(404);
header("X-Error: No SOUND(s) provided!");
die();
}
include "../data/config.php";
include "../lib/curlproxy.php";
$proxy = new proxy();
try{
$proxy->stream_linear_audio($_GET["s"]);
}catch(Exception $error){
header("X-Error: " . $error->getMessage());
}
-224
View File
@@ -1,224 +0,0 @@
<?php
new sc_audio();
class sc_audio{
public function __construct(){
include "../data/config.php";
include "../lib/curlproxy.php";
$this->proxy = new proxy();
if(isset($_GET["u"])){
/*
we're now proxying audio
*/
$viewkey = $_GET["u"];
if(!isset($_GET["r"])){
$this->do404("Ranges(r) are missing");
}
$ranges = explode(",", $_GET["r"]);
// sanitize ranges
foreach($ranges as &$range){
if(!is_numeric($range)){
$this->do404("Invalid range specified");
}
$range = (int)$range;
}
// sort ranges (just to make sure)
sort($ranges);
// convert ranges to pairs
$last = -1;
foreach($ranges as &$r){
$tmp = $r;
$r = [$last + 1, $r];
$last = $tmp;
}
$browser_headers = getallheaders();
// get the requested range from client
$client_range = 0;
foreach($browser_headers as $key => $value){
if(strtolower($key) == "range"){
preg_match(
'/bytes=([0-9]+)/',
$value,
$client_regex
);
if(isset($client_regex[1])){
$client_range = (int)$client_regex[1];
}else{
$client_range = 0;
}
break;
}
}
if(
$client_range < 0 ||
$client_range > $ranges[count($ranges) - 1][1]
){
// range is not satisfiable
http_response_code(416);
header("Content-Type: text/plain");
die();
}
$rng = null;
for($i=0; $i<count($ranges); $i++){
if($ranges[$i][0] <= $client_range){
$rng = $ranges[$i];
}
}
// proxy data!
http_response_code(206); // partial content
header("Accept-Ranges: bytes");
header("Content-Range: bytes {$rng[0]}-{$rng[1]}/" . ($ranges[count($ranges) - 1][1] + 1));
$viewkey =
preg_replace(
'/\/media\/([0-9]+)\/[0-9]+\/[0-9]+/',
'/media/$1/' . $rng[0] . '/' . $rng[1],
$viewkey
);
try{
$this->proxy->stream_linear_audio(
$viewkey
);
}catch(Exception $error){
$this->do404("Could not read stream");
}
die();
}
/*
redirect user to correct resource
we need to scrape and store the byte positions in the result URL
*/
if(!isset($_GET["s"])){
$this->do404("The URL(s) parameter is missing");
}
$viewkey = $_GET["s"];
if(
preg_match(
'/soundcloud\.com$/',
parse_url($viewkey, PHP_URL_HOST)
) === false
){
$this->do404("This endpoint can only be used for soundcloud streams");
}
try{
$json = $this->proxy->get($viewkey)["body"];
}catch(Exception $error){
$this->do404("Curl error: " . $error->getMessage());
}
$json = json_decode($json, true);
if(!isset($json["url"])){
$this->do404("Could not get URL from JSON");
}
$viewkey = $json["url"];
$m3u8 = $this->proxy->get($viewkey)["body"];
$m3u8 = explode("\n", $m3u8);
$lineout = null;
$streampos_arr = [];
foreach($m3u8 as $line){
$line = trim($line);
if($line[0] == "#"){
continue;
}
if($lineout === null){
$lineout = $line;
}
preg_match(
'/\/media\/[0-9]+\/([0-9]+)\/([0-9]+)/',
$line,
$matches
);
if(isset($matches[0])){
$streampos_arr[] = [
(int)$matches[1],
(int)$matches[2]
];
}
}
if($lineout === null){
$this->do404("Could not get stream URL");
}
$lineout =
preg_replace(
'/\/media\/([0-9]+)\/[0-9]+\/[0-9]+/',
'/media/$1/0/0',
$lineout
);
$streampos = [];
foreach($streampos_arr as $pos){
$streampos[] = $pos[1];
}
$streampos = implode(",", $streampos);
header("Location: /audio/sc?u=" . urlencode($lineout) . "&r=$streampos");
header("Accept-Ranges: bytes");
}
private function do404($error){
http_response_code(404);
header("Content-Type: text/plain");
header("X-Error: $error");
die();
}
}
-20
View File
@@ -1,20 +0,0 @@
<?php
if(!isset($_GET["s"])){
http_response_code(404);
header("X-Error: No SOUND(s) provided!");
die();
}
include "../data/config.php";
include "../lib/curlproxy.php";
$proxy = new proxy();
try{
$proxy->stream_linear_audio($_GET["s"]);
}catch(Exception $error){
header("X-Error: " . $error->getMessage());
}
-214
View File
@@ -1,214 +0,0 @@
<?php
include "../data/config.php";
new spotify();
class spotify{
public function __construct(){
include "../lib/fuckhtml.php";
$this->fuckhtml = new fuckhtml();
if(
!isset($_GET["s"]) ||
!preg_match(
'/^(track|episode)\.([A-Za-z0-9]{22})$/',
$_GET["s"],
$matches
)
){
$this->do404("The track ID(s) parameter is missing or invalid");
}
try{
if($matches[1] == "episode"){
$uri = "show";
}else{
$uri = $matches[1];
}
$embed =
$this->get("https://embed.spotify.com/{$uri}/" . $matches[2]);
}catch(Exception $error){
$this->do404("Failed to fetch embed data");
}
$this->fuckhtml->load($embed);
$json =
$this->fuckhtml
->getElementById(
"__NEXT_DATA__",
"script"
);
if($json === null){
$this->do404("Failed to extract JSON");
}
$json =
json_decode($json["innerHTML"], true);
if($json === null){
$this->do404("Failed to decode JSON");
}
switch($matches[1]){
case "track":
if(
isset(
$json
["props"]
["pageProps"]
["state"]
["data"]
["entity"]
["audioPreview"]
["url"]
)
){
header("Content-type: audio/mpeg");
header(
"Location: /audio/linear?s=" .
urlencode(
$json
["props"]
["pageProps"]
["state"]
["data"]
["entity"]
["audioPreview"]
["url"]
)
);
}else{
$this->do404("Could not extract playback URL");
}
break;
case "episode":
if(
isset(
$json
["props"]
["pageProps"]
["state"]
["data"]
["entity"]
["id"]
)
){
try{
$json =
$this->get(
"https://spclient.wg.spotify.com/soundfinder/v1/unauth/episode/" .
$json
["props"]
["pageProps"]
["state"]
["data"]
["entity"]
["id"] .
"/com.widevine.alpha"
);
}catch(Exception $error){
$this->do404("Failed to fetch audio resource");
}
$json = json_decode($json, true);
if($json === null){
$this->do404("Failed to decode audio resource JSON");
}
if(
isset($json["passthrough"]) &&
$json["passthrough"] == "ALLOWED" &&
isset($json["passthroughUrl"])
){
header(
"Location:" .
"/audio/linear.php?s=" .
urlencode(
str_replace(
"http://",
"https://",
$json["passthroughUrl"]
)
)
);
}else{
$this->do404("Failed to find passthroughUrl");
}
}else{
$this->do404("Failed to find episode ID");
}
break;
}
}
private function get($url){
$headers = [
"User-Agent: " . config::USER_AGENT,
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip",
"DNT: 1",
"Connection: keep-alive",
"Upgrade-Insecure-Requests: 1",
"Sec-Fetch-Dest: document",
"Sec-Fetch-Mode: navigate",
"Sec-Fetch-Site: none",
"Sec-Fetch-User: ?1"
];
$curlproc = curl_init();
curl_setopt($curlproc, CURLOPT_URL, $url);
curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
curl_setopt($curlproc, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
$data = curl_exec($curlproc);
if(curl_errno($curlproc)){
throw new Exception(curl_error($curlproc));
}
curl_close($curlproc);
return $data;
}
private function do404($error){
http_response_code(404);
header("Content-Type: text/plain");
header("X-Error: $error");
die();
}
}
+8
View File
@@ -0,0 +1,8 @@
# Specify API keys for the Google API in the following format:
# <key>
#
# Generate keys here:
# https://developers.google.com/custom-search/v1/overview
# Make sure to use a different Google account for each key, cause I'm
# pretty sure the ratelimit is on a per-account basis :P
#
+1
View File
@@ -0,0 +1 @@
# Paste Yep API keys here
+95 -28
View File
@@ -5,7 +5,7 @@ class config{
// any parameters. // any parameters.
// 4get version. Please keep this updated // 4get version. Please keep this updated
const VERSION = 8; const VERSION = 9;
// Will be shown pretty much everywhere. // Will be shown pretty much everywhere.
const SERVER_NAME = "4get"; const SERVER_NAME = "4get";
@@ -23,6 +23,48 @@ class config{
// Enable the API? // Enable the API?
const API_ENABLED = true; const API_ENABLED = true;
// Display errors. This should ONLY be enabled for debugging and constitutes a security risk.
const DISPLAY_ERRORS = false;
//
// 4play settings
//
// Enable 4get's /api/v2/provide_sesh endpoint
const FPLAY_ENABLE_API = false;
// 4play password. Please set this to something secure if you use this extension.
// This password is used to POST sessions to /api/v2/provide_sesh
// It's also used as the external endpoint password.
const FPLAY_PASSWORD = "cnc";
// Endpoint 4get hits to render webpages
// Eg: null=no endpoint provided
// Or "https://your-endpoint.com/my-endpoint" (excluding get parameters like &pw)
const FPLAY_EXTERNAL_ENDPOINT = null;
//
// RATELIMITING
// Limits also applies to the captcha page
//
// Amount of requests allowed before we block
// Set to 0 to disable ratelimiting
const RATELIMIT_BURST = 0;
// IP source for ratelimiting. Choices: false for getting the IP directly,
// or "X-Header-Name" to get the IP from a forwarded header
const RATELIMIT_IP_SOURCE = false;
// How long till the counter resets? Note: counter resets everytime
// a new request occurs while being blocked.
const RATELIMIT_TIME = 2;
// How many seconds to add every time a new request occurs while being blocked?
const RATELIMIT_PUNISH = 1;
// Add seconds of punishment time to users of the API?
const RATELIMIT_PUNISH_API = false;
// //
// BOT PROTECTION // BOT PROTECTION
// //
@@ -43,7 +85,7 @@ class config{
// If this regex expression matches on the user agent, it blocks the request // If this regex expression matches on the user agent, it blocks the request
// Not useful at all against a targetted attack // Not useful at all against a targetted attack
const HEADER_REGEX = '/bot|wget|curl|python-requests|scrapy|go-http-client|ruby|yahoo|spider|qwant/i'; const HEADER_REGEX = '/bot|wget|curl|python-requests|scrapy|go-http-client|ruby|yahoo|spider|qwant|meta/i';
// Block clients who present any of the following headers in their request (SPECIFY IN !!lowercase!!) // Block clients who present any of the following headers in their request (SPECIFY IN !!lowercase!!)
// Eg: ["x-forwarded-for", "x-via", "forwarded-for", "via"]; // Eg: ["x-forwarded-for", "x-via", "forwarded-for", "via"];
@@ -77,11 +119,9 @@ class config{
const MAX_SEARCHES = 100; const MAX_SEARCHES = 100;
// List of domains that point to your servers. Include your tor/i2p // List of domains that point to your servers. Include your tor/i2p
// addresses here! Must be a valid URL. Won't affect links placed on // addresses here! Must be a valid URL.
// the homepage. const ADDRESSES = [
const ALT_ADDRESSES = [ //"https://4get.ca",
//"https://4get.alt-tld",
//"http://4getwebfrq5zr4sxugk6htxvawqehxtdgjrbcn2oslllcol2vepa23yd.onion"
]; ];
// Known 4get instances. MUST use the https protocol if your instance uses // Known 4get instances. MUST use the https protocol if your instance uses
@@ -90,75 +130,102 @@ class config{
// eachother your serber should appear everywhere. // eachother your serber should appear everywhere.
const INSTANCES = [ const INSTANCES = [
"https://4get.ca", "https://4get.ca",
"https://4get.zzls.xyz", "https://search.fischbytes.de",
"https://4getus.zzls.xyz", "https://4get.nadeko.net",
"https://4get.silly.computer", "https://4get.silly.computer",
"https://4get.konakona.moe",
"https://4get.lvkaszus.pl",
"https://4g.ggtyler.dev",
"https://4get.perennialte.ch",
"https://4get.sijh.net", "https://4get.sijh.net",
"https://4get.hbubli.cc", "https://4get.hbubli.cc",
"https://4get.plunked.party",
"https://4get.seitan-ayoub.lol",
"https://4get.etenie.pl",
"https://4get.lunar.icu", "https://4get.lunar.icu",
"https://4get.dcs0.hu", "https://4get.dcs0.hu",
"https://4get.kizuki.lol",
"https://4get.psily.garden",
"https://search.milivojevic.in.rs",
"https://4get.snine.nl",
"https://4get.datura.network",
"https://4get.neco.lol", "https://4get.neco.lol",
"https://4get.lol",
"https://4get.ch",
"https://4get.edmateo.site", "https://4get.edmateo.site",
"https://4get.sudovanilla.org", "https://4get.sudovanilla.org",
"https://search.mint.lgbt" "https://search.mint.lgbt",
"https://4get.aishiteiru.moe",
"https://4.nboeck.de",
"https://search.yonderly.org",
"https://4get.lurx.net",
"https://4get.canine.tools",
"https://4get.sny.sh",
"https://4g.opnxng.com",
"https://s.307200.xyz",
"https://4get.privadency.com/"
]; ];
// Default user agent to use for scraper requests. Sometimes ignored to get specific webpages // Default user agent to use for scraper requests. Sometimes ignored to get specific webpages
// Changing this might break things. // Changing this might break things.
const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:129.0) Gecko/20100101 Firefox/129.0"; const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:157.0) Gecko/20100101 Firefox/157.0";
// User agent to use with 4get-friendly APIs
const USER_AGENT_FRIENDLY = "4get-scrapist (+https://4get.ca)";
// Proxy pool assignments for each scraper // Proxy pool assignments for each scraper
// false = Use server's raw IP // false = Use server's raw IP
// string = will load a proxy list from data/proxies // string = will load a proxy list from data/proxies
// Eg. "onion" will load data/proxies/onion.txt // Eg. "onion" will load data/proxies/onion.txt
const PROXY_DDG = false; // duckduckgo const PROXY_DDG = false; // duckduckgo
const PROXY_YAHOO = false;
const PROXY_YAHOO_JAPAN = false;
const PROXY_BRAVE = false; const PROXY_BRAVE = false;
const PROXY_FB = false; // facebook
const PROXY_GOOGLE = false; const PROXY_GOOGLE = false;
const PROXY_GOOGLE_ALT = false; // alt proxies for url resolution
const PROXY_GOOGLE_API = false;
const PROXY_GOOGLE_CSE = false;
const PROXY_STARTPAGE = false; const PROXY_STARTPAGE = false;
const PROXY_QWANT = false; const PROXY_QWANT = false;
const PROXY_BAIDU = false;
const PROXY_COCCOC = false;
const PROXY_GHOSTERY = false; const PROXY_GHOSTERY = false;
const PROXY_MARGINALIA = false; const PROXY_MARGINALIA = false;
const PROXY_MOJEEK = false; const PROXY_MOJEEK = false;
const PROXY_SC = false; // soundcloud const PROXY_SC = false; // soundcloud
const PROXY_SWISSCOWS = false;
const PROXY_SPOTIFY = false; const PROXY_SPOTIFY = false;
const PROXY_SOLOFIELD = false; const PROXY_SOLOFIELD = false;
const PROXY_PURILI = false;
const PROXY_WIBY = false; const PROXY_WIBY = false;
const PROXY_CURLIE = false; const PROXY_CURLIE = false;
const PROXY_YT = false; // youtube const PROXY_YT = false; // youtube
const PROXY_ARCHIVEORG = false;
const PROXY_SEPIASEARCH = false;
const PROXY_ODYSEE = false;
const PROXY_DAILYMOTION = false;
const PROXY_VIMEO = false;
const PROXY_YEP = false; const PROXY_YEP = false;
const PROXY_PINTEREST = false; const PROXY_PINTEREST = false;
const PROXY_FLICKR = false;
const PROXY_PIXABAY = false;
const PROXY_UNSPLASH = false;
const PROXY_PEXELS = false;
const PROXY_FIVEHPX = false;
const PROXY_VSCO = false;
const PROXY_SEZNAM = false; const PROXY_SEZNAM = false;
const PROXY_NAVER = false; const PROXY_NAVER = false;
const PROXY_GREPPR = false;
const PROXY_CROWDVIEW = false;
const PROXY_MWMBL = false; const PROXY_MWMBL = false;
const PROXY_FTM = false; // findthatmeme const PROXY_FTM = false; // findthatmeme
const PROXY_IMGUR = false; const PROXY_IMGUR = false;
const PROXY_CARA = false;
const PROXY_YANDEX_W = false; // yandex web const PROXY_YANDEX_W = false; // yandex web
const PROXY_YANDEX_I = false; // yandex images const PROXY_YANDEX_I = false; // yandex images
const PROXY_YANDEX_V = false; // yandex videos const PROXY_YANDEX_V = false; // yandex videos
const PROXY_SAFEBOORU = false;
const PROXY_KONACHAN = false;
const PROXY_YANDERE = false;
const PROXY_TBIB = false;
const PROXY_GELBOORU = false;
// //
// Scraper-specific parameters // Scraper-specific parameters
// //
// GOOGLE CSE & GOOGLE API
const GOOGLE_CX_ENDPOINT = "d4e68b99b876541f0";
// MARGINALIA // MARGINALIA
// Use "null" to default out to HTML scraping OR specify a string to // Use "null" to default out to HTML scraping OR specify a string to
// use the API (Eg: "public"). API has less filters. // use the API (Eg: "public"). API has less filters.
const MARGINALIA_API_KEY = null; const MARGINALIA_API_KEY = null;
// Yep
const YEP_USE_API = false;
} }
+6 -5
View File
@@ -6,14 +6,15 @@ services:
image: luuul/4get:latest image: luuul/4get:latest
restart: unless-stopped restart: unless-stopped
environment: environment:
- FOURGET_PROTO=http
- FOURGET_SERVER_NAME=4get.ca - FOURGET_SERVER_NAME=4get.ca
- FOURGET_INSTANCES=https://4get.ca
ports: ports:
- "80:80" - "80:80"
- "443:443" - "443:443"
volumes: # volumes:
- /etc/letsencrypt/live/domain.tld:/etc/4get/certs # - /etc/letsencrypt/live/domain.tld:/etc/4get/certs # mount ssl
# mount custom banners and captcha # - ./banners:/var/www/html/4get/banner # mount custom banners
- ./banners:/var/www/html/4get/banner # - ./captcha:/var/www/html/4get/data/captcha # mount captcha images
- ./captcha:/var/www/html/4get/data/captcha
+1
View File
@@ -0,0 +1 @@
# intentionally blank
File renamed without changes.
File renamed without changes.
File renamed without changes.
+12 -3
View File
@@ -8,18 +8,27 @@ FOURGET_PROTO="${FOURGET_PROTO#\"}"
# make lowercase # make lowercase
FOURGET_PROTO=`echo $FOURGET_PROTO | awk '{print tolower($0)}'` FOURGET_PROTO=`echo $FOURGET_PROTO | awk '{print tolower($0)}'`
FOURGET_SRC='/var/www/html/4get'
mkdir -p /etc/apache2
if [ "$FOURGET_PROTO" = "https" ]; then if [ "$FOURGET_PROTO" = "https" ]; then
echo "Using https configuration" echo "Using https configuration"
cp /etc/apache2/https.conf /etc/apache2/httpd.conf cp -r ${FOURGET_SRC}/docker/apache/https/httpd.conf /etc/apache2
cp -r ${FOURGET_SRC}/docker/apache/https/conf.d/* /etc/apache2/conf.d
else else
echo "Using http configuration" echo "Using http configuration"
cp /etc/apache2/http.conf /etc/apache2/httpd.conf cp -r ${FOURGET_SRC}/docker/apache/http/httpd.conf /etc/apache2
cp -r ${FOURGET_SRC}/docker/apache/http/conf.d/* /etc/apache2/conf.d
fi fi
php ./docker/gen_config.php php ./docker/gen_config.php
if [ "$@" = "start" ]; then
echo "4get is running" echo "4get is running"
exec httpd -DFOREGROUND exec httpd -DFOREGROUND
else
exec "$@"
fi
+2 -1
View File
@@ -13,7 +13,8 @@ $fourget_env = array_filter($env, function($v, $k) {
foreach($fourget_env as $key => $val) { foreach($fourget_env as $key => $val) {
$target_key = preg_replace('/^FOURGET_/', '', $key); $target_key = preg_replace('/^FOURGET_/', '', $key);
$from_env[$target_key] = trim($val, '\'"'); $val = trim($val, '\'"');
$from_env[$target_key] = match(strtolower($val)) { "true" => true, "false" => false, default => $val };
}; };
$merged_config = array_merge($from_config, $from_env); $merged_config = array_merge($from_config, $from_env);
+159 -164
View File
@@ -1,31 +1,16 @@
# Sample Apache2 configuration # Sample Apache2 configuration
This is the apache2 configuration file used on the 4get.ca official instance, in hopes that it's useful to you! This is the apache2 configuration file used on the 4get.ca official instance, in hopes that it's useful to you!
Apache2 now sits behind haproxy, so it's configured to accept plain HTTP traffic on port `8080`. I highly recommend you setup haproxy, since apache2 is pretty slow at handling SSL/HTTP2. A sample haproxy config has been added at the end of this document as a reference.
Looking for the apache2 guide? <a href="https://git.lolcat.ca/lolcat/4get/src/branch/master/docs/apache2.md">go here.</a>. Looking for the apache2 guide? <a href="https://git.lolcat.ca/lolcat/4get/src/branch/master/docs/apache2.md">go here.</a>.
```xml ```xml
<VirtualHost *:443> <VirtualHost *:8080>
ServerName www.4get.ca
SSLEngine On
SSLCertificateFile /etc/letsencrypt/live/4get.ca/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/4get.ca/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/4get.ca/chain.pem
RedirectMatch 301 ^(.*)$ https://4get.ca$1
</VirtualHost>
<VirtualHost *:443>
ServerName 4get.ca ServerName 4get.ca
ServerAlias 4getwebfrq5zr4sxugk6htxvawqehxtdgjrbcn2oslllcol2vepa23yd.onion
ServerAdmin will@lolcat.ca # compress
DocumentRoot /var/www/4get
SSLEngine On
SSLOptions +StdEnvVars
#ErrorLog ${APACHE_LOG_DIR}/error.log
AddOutputFilterByType DEFLATE application/json AddOutputFilterByType DEFLATE application/json
AddOutputFilterByType DEFLATE application/javascript AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript AddOutputFilterByType DEFLATE application/x-javascript
@@ -33,163 +18,173 @@ Looking for the apache2 guide? <a href="https://git.lolcat.ca/lolcat/4get/src/br
AddOutputFilterByType DEFLATE text/plain AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/css AddOutputFilterByType DEFLATE text/css
SSLCertificateFile /etc/letsencrypt/live/4get.ca/fullchain.pem DocumentRoot /var/www/4get
SSLCertificateKeyFile /etc/letsencrypt/live/4get.ca/privkey.pem ErrorLog ${APACHE_LOG_DIR}/error.log
SSLCertificateChainFile /etc/letsencrypt/live/4get.ca/chain.pem CustomLog /dev/null combined
#CustomLog /var/log/apache2/access.log combined
<FilesMatch \.php$>
SetHandler "proxy:unix:/run/php/php-fpm.sock|fcgi://localhost/"
</FilesMatch>
<Directory /var/www/4get> <Directory /var/www/4get>
Options -MultiViews Options -MultiViews
AllowOverride All AllowOverride All
Require all granted Require all granted
# prevent 404s from hitting php-fpm
RewriteEngine On RewriteEngine On
RewriteCond %{REQUEST_URI} \.php$ [NC]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ - [R=404,L]
# prevent /index hits
RewriteRule ^index$ - [R=404,L]
# 404 direct .php file access
RewriteCond %{THE_REQUEST} "\s/+[^?\s]*\.php(?:/|[?\s])" [NC]
RewriteRule ^ - [R=404,L]
# internally redirect to non-php extension
RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME}.php -f
RewriteRule ^([^\.]+)$ $1.php [NC,L] RewriteRule ^([^\.]+)$ $1.php [NC,L]
</Directory> </Directory>
# deny access to private resources # deny access to private resources
<Directory /var/www/4get/data/> <DirectoryMatch "^/var/www/4get/(data|lib|scraper|template|oracles|docs|docker|extra|resolve)/">
Order Deny,allow Require all denied
Deny from all </DirectoryMatch>
<Directory /var/www/4get/icons>
Options -Indexes
</Directory> </Directory>
</VirtualHost> </VirtualHost>
```
<VirtualHost *:443>
ServerName www.lolcat.ca # Haproxy configuration
SSLEngine On ```cfg
SSLCertificateFile /etc/letsencrypt/live/4get.ca/fullchain.pem global
SSLCertificateKeyFile /etc/letsencrypt/live/4get.ca/privkey.pem #log /dev/log local0
SSLCertificateChainFile /etc/letsencrypt/live/4get.ca/chain.pem log /dev/log local0 warning
log /dev/log local1 notice
RedirectMatch 301 ^(.*)$ https://lolcat.ca$1 chroot /var/lib/haproxy
</VirtualHost> stats socket /run/haproxy/admin.sock mode 660 level admin
stats timeout 30s
<VirtualHost *:443> user haproxy
ServerName lolcat.ca group haproxy
daemon
ServerAdmin will@lolcat.ca
DocumentRoot /var/www/lolcat # Default SSL material locations
ca-base /etc/ssl/certs
SSLEngine On crt-base /etc/ssl/private
SSLOptions +StdEnvVars
# See: https://ssl-config.mozilla.org/#server=haproxy&server-version=2.0.3&config=intermediate
#ErrorLog ${APACHE_LOG_DIR}/error.log #ssl-default-bind-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:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl-default-bind-ciphers HIGH:!aNULL
AddOutputFilterByType DEFLATE application/json ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
AddOutputFilterByType DEFLATE application/javascript ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
AddOutputFilterByType DEFLATE application/x-javascript
AddOutputFilterByType DEFLATE text/html # ja4 settings
AddOutputFilterByType DEFLATE text/plain tune.lua.bool-sample-conversion normal
AddOutputFilterByType DEFLATE text/css tune.ssl.capture-buffer-size 190
lua-load /etc/haproxy/lua/ja4.lua
SSLCertificateFile /etc/letsencrypt/live/4get.ca/fullchain.pem defaults
SSLCertificateKeyFile /etc/letsencrypt/live/4get.ca/privkey.pem log global
SSLCertificateChainFile /etc/letsencrypt/live/4get.ca/chain.pem mode http
#option httplog
<Directory /var/www/lolcat> option dontlognull
Options -MultiViews timeout connect 5000
AllowOverride All timeout client 50000
Require all granted timeout server 50000
errorfile 400 /etc/haproxy/errors/400.http
RewriteEngine On errorfile 403 /etc/haproxy/errors/403.http
RewriteCond %{REQUEST_FILENAME} !-d errorfile 408 /etc/haproxy/errors/408.http
RewriteCond %{REQUEST_FILENAME} !-f errorfile 500 /etc/haproxy/errors/500.http
RewriteRule ^([^\.]+)$ $1.php [NC,L] errorfile 502 /etc/haproxy/errors/502.http
</Directory> errorfile 503 /etc/haproxy/errors/503.http
</VirtualHost> errorfile 504 /etc/haproxy/errors/504.http
<VirtualHost *:443> frontend http_80_in
ServerName www.nyym.co # bind tcp ip4/6
bind *:80
SSLEngine On bind [::]:80
SSLCertificateFile /etc/letsencrypt/live/nyym.co/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/nyym.co/privkey.pem mode http
SSLCertificateChainFile /etc/letsencrypt/live/nyym.co/chain.pem
# handle ip.lolcat.ca+onion addy on plain HTTP
RedirectMatch 301 ^(.*)$ https://nyym.co$1 acl is_ip_host hdr(host) -i ip.lolcat.ca ip4.lolcat.ca ip6.lolcat.ca 4getwebfrq5zr4sxugk6htxvawqehxtdgjrbcn2oslllcol2vepa23yd.onion
</VirtualHost>
# redirect to https
<VirtualHost *:443> http-request redirect scheme https code 301 if { hdr(host) -i lolcat.ca }
ServerName nyym.co http-request redirect scheme https code 301 if { hdr(host) -i git.lolcat.ca }
http-request redirect scheme https code 301 if { hdr(host) -i lick.lolcat.ca }
ServerAdmin will@lolcat.ca http-request redirect scheme https code 301 if { hdr(host) -i cf-tls.lolcat.ca }
DocumentRoot /var/www/nyym http-request redirect scheme https code 301 if { hdr(host) -i cf-js.lolcat.ca }
http-request redirect scheme https code 301 if { hdr(host) -i cf-captcha.lolcat.ca }
SSLEngine On
SSLOptions +StdEnvVars # redirect www.lolcat.ca to lolcat.ca
http-request redirect location https://lolcat.ca%[capture.req.uri] code 301 if { hdr(host) -i www.lolcat.ca }
#ErrorLog ${APACHE_LOG_DIR}/error.log
# everything else redirects to 4get.ca
AddOutputFilterByType DEFLATE application/json http-request redirect location https://4get.ca%[capture.req.uri] code 301 unless is_ip_host
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript # ... unless its an ip host, or the onion domain
AddOutputFilterByType DEFLATE text/html use_backend apache if is_ip_host
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/css frontend https_443_in
# bind tcp ip4/6
SSLCertificateFile /etc/letsencrypt/live/nyym.co/fullchain.pem bind *:443 ssl crt /etc/apache2/ssl/4get-haproxy.pem crt /etc/apache2/ssl/lolcat-haproxy.pem
SSLCertificateKeyFile /etc/letsencrypt/live/nyym.co/privkey.pem bind [::]:443 ssl crt /etc/apache2/ssl/4get-haproxy.pem crt /etc/apache2/ssl/lolcat-haproxy.pem
SSLCertificateChainFile /etc/letsencrypt/live/nyym.co/chain.pem
# QUIC is disabled. See https://github.com/haproxy/haproxy/issues/3529
<Directory /var/www/nyym> # bind udp ip4/6
Options -MultiViews #bind quic4@*:443 ssl crt /etc/apache2/ssl/4get-haproxy.pem crt /etc/apache2/ssl/lolcat-haproxy.pem alpn h3
AllowOverride All #bind quic6@[::]:443 ssl crt /etc/apache2/ssl/4get-haproxy.pem crt /etc/apache2/ssl/lolcat-haproxy.pem alpn h3
Require all granted
# tell client we support http3 now
RewriteEngine On #http-response set-header Alt-Svc "h3=\":443\"; ma=86400"
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f mode http
RewriteRule ^([^\.]+)$ $1.php [NC,L]
</Directory> # redirect all unknown domains to 4get.ca
</VirtualHost> acl valid_https_hosts hdr(host) -i 4get.ca lolcat.ca ip.lolcat.ca ip4.lolcat.ca ip6.lolcat.ca git.lolcat.ca lick.lolcat.ca cf-tls.lolcat.ca cf-js.lolcat.ca cf-captcha.lolcat.ca www.lolcat.ca
<VirtualHost *:443> # add onion domain to 4get.ca
ServerName git.lolcat.ca acl is_4get hdr(host) -i 4get.ca
http-request set-var(txn.is_4get) bool(true) if is_4get
SSLEngine On http-response set-header Onion-Location http://4getwebfrq5zr4sxugk6htxvawqehxtdgjrbcn2oslllcol2vepa23yd.onion%[capture.req.uri] if { var(txn.is_4get) -m bool }
SSLOptions +StdEnvVars
# git.lolcat.ca -> gitea service
#ErrorLog ${APACHE_LOG_DIR}/error.log acl acl_gitea hdr(host) -i git.lolcat.ca
AddOutputFilterByType DEFLATE application/json # redirects
AddOutputFilterByType DEFLATE application/javascript http-request redirect location https://4get.ca%[capture.req.uri] code 301 unless valid_https_hosts
AddOutputFilterByType DEFLATE application/x-javascript http-request redirect location https://lolcat.ca%[capture.req.uri] code 301 if { hdr(host) -i www.lolcat.ca }
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/plain # add security headers
AddOutputFilterByType DEFLATE text/css http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
http-response set-header X-Content-Type-Options "nosniff"
SSLCertificateFile /etc/letsencrypt/live/4get.ca/fullchain.pem http-response set-header X-Frame-Options "SAMEORIGIN"
SSLCertificateKeyFile /etc/letsencrypt/live/4get.ca/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/4get.ca/chain.pem # route
use_backend gitea if acl_gitea
ProxyPreserveHost On default_backend apache
ProxyRequests off
AllowEncodedSlashes NoDecode backend apache
ProxyPass / http://localhost:3000/ nocanon # pass ja4
</VirtualHost> http-request lua.fingerprint_ja4 if { ssl_fc }
http-request set-header X-Ja4 %[var(txn.fingerprint_ja4)] if { ssl_fc }
<VirtualHost *:443>
ServerName live.lolcat.ca # delete server header
http-response del-header Server
ServerAdmin will@lolcat.ca
DocumentRoot /var/www/live # pass remote IP
option forwardfor
SSLEngine On server apache_server 127.0.0.1:8080 check
SSLOptions +StdEnvVars
backend gitea
#ErrorLog ${APACHE_LOG_DIR}/error.log server gitea_server 127.0.0.1:3000 check
AddOutputFilterByType DEFLATE application/json
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/x-javascript
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/css
SSLCertificateFile /etc/letsencrypt/live/4get.ca/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/4get.ca/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/4get.ca/chain.pem
</VirtualHost>
``` ```
+40 -8
View File
@@ -7,27 +7,35 @@ Then, install the following dependencies:
```sh ```sh
apt update apt update
apt upgrade apt upgrade
apt install php-mbstring apache2 certbot php-imagick imagemagick php-curl curl php-apcu git libapache2-mod-php apt install php-mbstring apache2 certbot php-imagick imagemagick php-curl curl php-apcu git libapache2-mod-fcgid php-fpm
``` ```
Enable the required modules: Enable the required modules:
```sh ```sh
a2dismod mpm_prefork
a2enmod mpm_event
a2enmod ssl a2enmod ssl
a2enmod rewrite a2enmod rewrite
a2enmod proxy_fcgi setenvif actions alias
a2enmod http2
a2enmod headers
a2enmod proxy
``` ```
And enable these optional ones, which might be useful to you later on. The `proxy` module is useful for setting up reverse proxies to services like gitea, and `headers` is useful to tweak global header values: Tune the performance of php-fpm. You will need to edit this file according to your server specs and number of users. Edit the file at `/etc/php/8.4/pool.d/www.conf`:
```sh ```sh
a2enmod proxy pm = static
a2enmod headers pm.max_children = 50
``` ```
These values are what I currently use on 4get.ca, but for personal use, you can set `pm` to `ondemand` and `pm.max_children` to `20` (if you want those thumbnails to load fast!)
Now, restart apache2: Now, restart apache2:
```sh ```sh
service apache2 restart service apache2 restart
``` ```
Just for good measure, please check if your webserver is running. Access it through HTTP, not HTTPS. You should see the apache2 default landing page. Just for good measure, please check if your webserver is running. Access it through HTTP, not HTTPS. You should see the apache2 default landing page. Just a note, http2 won't work just yet since you don't have SSL yet.
## 000-default.conf ## 000-default.conf
Now, edit the following file: `/etc/apache2/sites-available/000-default.conf`, remove everything and carefully add each rule specified here, while making sure to replace my domains with your own: Now, edit the following file: `/etc/apache2/sites-available/000-default.conf`, remove everything and carefully add each rule specified here, while making sure to replace my domains with your own:
@@ -74,16 +82,35 @@ Now, edit the following file: `/etc/apache2/sites-available/000-default.conf`, r
DocumentRoot /var/www/4get DocumentRoot /var/www/4get
<FilesMatch \.php$>
SetHandler "proxy:unix:/run/php/php8.1-fpm.sock|fcgi://localhost/"
</FilesMatch>
Options -MultiViews Options -MultiViews
RewriteEngine On RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^\.]+)$ $1.php [NC,L] RewriteRule ^([^\.]+)$ $1.php [NC,L]
<Directory /var/www/4get>
Options -MultiViews
AllowOverride All
Require all granted
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^([^\.]+)$ $1.php [NC,L]
</Directory>
# deny access to private resources # deny access to private resources
<Directory /var/www/4get/data/> <DirectoryMatch "^/var/www/4get/(data|lib|scraper|template|oracles|docs|docker|extra|resolve)/">
Order Deny,allow Require all denied
Deny from all </DirectoryMatch>
# remove directory listing from /icons
<Directory /var/www/4get/icons>
Options -Indexes
</Directory> </Directory>
</VirtualHost> </VirtualHost>
``` ```
@@ -116,6 +143,7 @@ Make sure to replace `4get.ca` with your own domain under the `SSLCertificate*`
ServerAdmin will@lolcat.ca ServerAdmin will@lolcat.ca
DocumentRoot /var/www/4get DocumentRoot /var/www/4get
Protocols h2 http/1.1
SSLEngine On SSLEngine On
SSLOptions +StdEnvVars SSLOptions +StdEnvVars
@@ -128,6 +156,10 @@ Make sure to replace `4get.ca` with your own domain under the `SSLCertificate*`
AddOutputFilterByType DEFLATE text/plain AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/css AddOutputFilterByType DEFLATE text/css
<FilesMatch \.php$>
SetHandler "proxy:unix:/run/php/php8.1-fpm.sock|fcgi://localhost/"
</FilesMatch>
SSLCertificateFile /etc/letsencrypt/live/4get.ca/fullchain.pem SSLCertificateFile /etc/letsencrypt/live/4get.ca/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/4get.ca/privkey.pem SSLCertificateKeyFile /etc/letsencrypt/live/4get.ca/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/4get.ca/chain.pem SSLCertificateChainFile /etc/letsencrypt/live/4get.ca/chain.pem
+124 -21
View File
@@ -9,34 +9,47 @@ Welcome! This guide assumes that you have a working 4get instance. This will hel
4. The captcha font is located in `data/fonts/captcha.ttf` 4. The captcha font is located in `data/fonts/captcha.ttf`
# Cloudflare bypass (TLS check) # Cloudflare bypass (TLS check)
**Note: this only allows you to bypass the browser integrity checks. Captchas & javascript challenges will not be bypassed.** >These instructions have been updated to work with Debian 13 Trixie.
Configuring this lets you fetch images sitting behind Cloudflare and allows you to scrape the **Yep** & the **Mwmbl** search engines. Please be aware that APT will fight against you and will re-install the openSSL-version of curl constantly when updating. **Note: this only allows you to bypass the browser integrity checks. Captchas & javascript challenges will not be bypassed by this program!**
First, follow these instructions. Only install the Firefox modules: Configuring this lets you fetch images sitting behind Cloudflare and allows you to scrape the **Yep** search engine.
https://github.com/lwthiker/curl-impersonate/blob/main/INSTALL.md#native-build To come up with this set of instructions, I used [this guide](https://github.com/lwthiker/curl-impersonate/blob/main/INSTALL.md#native-build) as a reference, but trust me you probably want to stick to what's written on this page.
Once you did this, you should be able to run the following inside your terminal:
First, compile curl-impersonate (the firefox flavor).
```sh ```sh
$ curl_ff117 --version git clone https://github.com/lwthiker/curl-impersonate/
curl 8.1.1 (x86_64-pc-linux-gnu) libcurl/8.1.1 NSS/3.92 zlib/1.2.13 brotli/1.0.9 zstd/1.5.4 libidn2/2.3.3 nghttp2/1.56.0 cd curl-impersonate
Release-Date: 2023-05-23 sudo apt install build-essential pkg-config cmake ninja-build curl autoconf automake libtool python3-pip libnss3 libnss3-dev
Protocols: dict file ftp ftps gopher gophers http https imap imaps mqtt pop3 pop3s rtsp smb smbs smtp smtps telnet tftp ws wss mkdir build
Features: alt-svc AsynchDNS brotli HSTS HTTP2 HTTPS-proxy IDN IPv6 Largefile libz NTLM NTLM_WB SSL threadsafe UnixSockets zstd cd build
``` ../configure
Now, after compiling, you should have a `libcurl-impersonate-ff.so` sitting somewhere. Mine (on my debian install) is located at `/usr/local/lib/libcurl-impersonate-ff.so`. make firefox-build
sudo make firefox-install
Find the `libcurl.so.4` file used by your current installation of curl. For me, this file is located at `/usr/lib/x86_64-linux-gnu/libcurl.so.4` sudo ldconfig
Now comes the sketchy part: replace `libcurl.so.4` with `libcurl-impersonate-ff.so`. You can do this in the following way:
```sh
sudo rm /usr/lib/x86_64-linux-gnu/libcurl.so.4
sudo cp /usr/local/lib/libcurl-impersonate-ff.so /usr/lib/x86_64-linux-gnu/libcurl.so.4
``` ```
Make sure to restart your webserver and/or PHP daemon, otherwise it will keep using the old library. You should now be able to bypass Cloudflare's shitty checks!! Now, after compiling, you should have a `libcurl-impersonate-ff.so` sitting somewhere. Mine is located at `/usr/local/lib/libcurl-impersonate-ff.so`. Patch your PHP install so that it loads the right library:
```sh
sudo systemctl edit php8.4-fpm.service
```
^This will open a text editor. Add the following shit in there, in between those 2 comments I pasted for ya just for reference:
```sh
### Editing /etc/systemd/system/php8.4-fpm.service.d/override.conf
### Anything between here and the comment below will become the contents of the>
[Service]
Environment="LD_PRELOAD=/usr/local/lib/libcurl-impersonate-ff.so"
Environment="CURL_IMPERSONATE=firefox117"
### Edits below this comment will be discarded
```
Restart php8.4-fpm. (`sudo service php8.4-fpm restart`). To test things out, try making a search on "Yep", they check for SSL. If you get results (or a timeout, this piece of shit engine is slow as fuck) that means it works!
# Robots.txt # Robots.txt
Make sure you configure this right to optimize your search engine presence! Head over to `/robots.txt` and change the 4get.ca domain to your own domain. Make sure you configure this right to optimize your search engine presence! Head over to `/robots.txt` and change the 4get.ca domain to your own domain.
@@ -66,3 +79,93 @@ Done! The scraper you chose should now be using the rotating proxies. When askin
## Important! ## Important!
If you ever test out a `socks5` proxy locally on your machine and find out it works but doesn't on your server, try supplying the `socks5_hostname` protocol instead. Hopefully this tip can save you 3 hours of your life! If you ever test out a `socks5` proxy locally on your machine and find out it works but doesn't on your server, try supplying the `socks5_hostname` protocol instead. Hopefully this tip can save you 3 hours of your life!
# 4play setup
4play is a Firefox extension I wrote that lets you control a browser from 4get. When you do a search, 4get sends a query to a nodeJS http server that communicates to the browser via websockets. The browser then reports back various payloads used for scraping. It's very similar to tools like puppeteer or playwright, except it uses websockets to communicate with the browser instead of the browser's debugger. This is needed to prevent `navigator.webdriver` from being set to `true` (also avoids a fuckton of other botchecks, that's just the tip of the iceberg).
## Requirements
- A dedicated laptop or computer running a full desktop environment (like Windows, Mac, or Linux DEs)
- Something to make the computer think it's connected to a screen. You need it, otherwise you'll fallback to software rendering and sites can detect that. You can use:
- Your laptop's screen
- A turned-on screen connected via HDMI
- An EDID adapter to simulate a display (no affiliate links: [HDMI](https://www.aliexpress.com/item/1005011668390064.html), [DisplayPort](https://www.aliexpress.com/item/1005011659561729.html))
- Firefox ESR or the latest version of Firefox
- The [4play extension](https://addons.mozilla.org/en-US/firefox/addon/4play/) installed
## Installation
Install the dependencies on the machine you wish to run 4play on. In a sane setup, you usually want 4play to run on a dedicated user-grade machine. For the sake of simplicity, this machine will be referred as the "render server".
```sh
git clone https://git.lolcat.ca/lolcat/4get
cd 4get/extra/4play
# install nodejs 26.x or later if it's not already installed
npm install @lawlers/4play
sudo npm install -g nodemon
# edit line 7 where it says 'var password = "cnc";'. Use a strong password.
nano page-render.js
# test the server
nodemon page-render.js
```
I recommend setting up a systemd service to auto-start the 4play server:
```sh
sudo nano /etc/systemd/system/fplay.service
```
Paste this in (make sure to update `User` & `WorkingDirectory`)
```sh
[Unit]
Description=4play
After=network.target
[Service]
Type=simple
User=will
WorkingDirectory=/home/will/Desktop/4get/extra/4play/
ExecStart=nodemon /home/will/Desktop/4get/extra/4play/page-render.js
Restart=always
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.target
```
Now, enable the service
```sh
sudo systemctl enable fplay
sudo service fplay start
sudo service fplay status # check the status. You should see it running
```
This should expose a webservice located at `http://localhost:3000`. I highly recommend you put this endpoint behind apache2 or nginx to add SSL, if the render server sits on a different network.
## Setting up Firefox
- Install Firefox. You can go for the ESR version or the latest version. You'll figure it out.
- Head over to [this page](https://addons.mozilla.org/en-US/firefox/addon/4play/) or search "4play" on the Firefox extension store and install the extension.
- Click the settings cog next to the extension name. Enable "Run in Private Windows" and "Allow automatic updates"
- Click the extension in the top bar. Modify the `ws://localhost:3030/cnc` URL: replace the `cnc` path with the password you set earlier for the server.
- Use a timeout value of `30000` to avoid getting throttled by Firefox during reconnections.
If done correctly, you should see the red dot turn green in the extension's icon. You're ready to load pages!
**IMPORTANT:** You will want to turn off search history to protect yourself and your users. Head to `about:preferences` and search for "history". Under the "History" category, pick "Customize category", then click "Choose what you want Firefox to remember". In that sub-menu, untick everything. Make sure to **NOT** turn on the "Always use private browsing mode" option, as it will prevent the 4play extension from creating containers.
## Setting up 4get
Now, you need to tell 4get to use the 4play service. Edit `data/config.php` and change these:
```php
const FPLAY_PASSWORD = "cnc"; # Change this to your password
const FPLAY_EXTERNAL_ENDPOINT = null; # Change null to "https://your-endpoint.com/api/example";, with the quotes and semicolon
```
**IMPORTANT:** By default, `localhost:3030` is what Firefox connects to. The render server is exposed at `localhost:3000`. Don't mix them up, otherwise you will get errors about an "invalid Upgrade header".
If everything was done correctly, you should be seeing results on the Google scraper! You can set proxies in `data/config.php` for it, just like any other scraper.
In case it isn't clear, here is a visual representation of what you want to achieve:
![Network map](https://i.imgur.com/jFjbBtG.png)
+36
View File
@@ -0,0 +1,36 @@
# Develop
Start the PHP webserver like this, assuming you have curl-impersonate installed already
```sh
LD_PRELOAD=/usr/local/lib/libcurl-impersonate-ff.so CURL_IMPERSONATE=ff117 php -S localhost:8000
```
Make sure to add `.php` to paths, the embedded PHP webserver doesnt support `mod_rewrite`-like directives.
If, for some reason, you're in an environment where accessing the debug logs is impossible (like those shitty shared webhosts), you can set `DISPLAY_ERRORS` to `true` in `data/config.php`.
# Debug guide for docker
Install:
```sh
git clone https://git.lolcat.ca/lolcat/4get
# make changes to docker/apache/http/httpd.conf (comment out the lines that disable logging)
docker build -t luuul/4get .
```
Run:
```sh
docker run --name 4get -p 80:80 -p 443:443 luuul/4get
```
Get debug logs:
```sh
docker exec -it `docker ps -qf ancestor=luuul/4get:latest` sh # browse the container
docker exec -it `docker ps -qf ancestor=luuul/4get:latest` tail -f /var/log/apache2/error_log
```
Restart the container:
```sh
docker restart 4get
```
+128 -37
View File
@@ -1,27 +1,67 @@
# Install on NGINX <h1 align=center>Installation of 4get in NGINX</h1>
>I do NOT recommend following this guide, only follow this if you *really* need to use nginx. I recommend you use the apache2 steps instead. <div align=right>
Login as root. > NOTE: As the previous version stated, it is better to follow the <a href="https://git.lolcat.ca/lolcat/4get/src/branch/master/docs/apache2.md">Apache2 guide</a> instead of the Nginx one.
Create a file in `/etc/nginx/sites-avaliable/` called `4get.conf` or any name you want and put this into the file: > NOTE: This is going to guess that you're using either a <abbr title="(Arch Linux, Artix Linux, Endeavouros, etc...) ">Arch-based system</abbr> or a <abbr title="(Debian, Ubuntu, Devuan, etc...)">Debian-based system</abbr>, although you can still follow it with minor issues.
</div>
1. Login as root.
2. Upgrade your system:
* On Arch-based, run `pacman -Syu`.
* On Debian-based, run `apt update`, then `apt upgrade`.
3. Install the following dependencies:
* `git`: So you can clone <a href="https://git.lolcat.ca/lolcat/4get">this</a> repository.
* `nginx`: So you can run Nginx.
* `php-fpm`: This is what allows Nginx to run *(and show)* PHP files.
* `php-imagick`, `imagemagick`: Image manipulation.
* `php-apcu`: Caching module.
* `php-curl`, `curl`: Transferring data with URLs.
* `php-mbstring`: String utils.
* `certbot`, `certbot-nginx`: ACME client. Used to create SSL certificates.
* In Arch-based distributions:
* `pacman -S nginx certbot php-imagick certbot-nginx imagemagick curl php-apcu git`
* In Debian-based distributions:
* `apt install php-mbstring nginx certbot-nginx certbot php-imagick imagemagick php-curl curl php-apcu git`
<div align=right>
> IMPORTANT: `php-curl`, `php-mbstring` might be a Debian-only package, but this needs further fact checking.
> IMPORTANT: If having issues with `php-apcu` or `libsodium`, go to [^1].
</div>
4. `cd` to `/etc/nginx` and make the `conf.d/` directory if it doesn't exist:
* Again, this guesses you're logged in as root.
```sh
cd /etc/nginx
ls -l conf.d/ # If ls shows conf.d, then it means it exists.
# If it does not, run:
mkdir conf.d
``` ```
5. Make a file inside `conf.d/` called `4get.conf` and place the following content:
* First run `touch conf.d/4get.conf` then `nano conf.d/4get.conf` to open the nano editor: *(Install it if it is not, or use another editor.)*
```sh
server { server {
# DO YOU REALLY NEED TO LOG SEARCHES? access_log /dev/null; # Search log file. Do you really need to?
access_log /dev/null; error_log /dev/null; # Error log file.
error_log /dev/null;
# Change this if you have 4get in other folder. # Change this if you have 4get in another folder.
root /var/www/4get; root /var/www/4get;
# Change yourdomain by your domain lol # Change 'yourdomain' to your domain.
server_name www.yourdomain.com yourdomain.com; server_name www.yourdomain.com yourdomain.com;
# Port to listen to.
listen 80;
location @php { location @php {
try_files $uri.php $uri/index.php =404; try_files $uri.php $uri/index.php =404;
# Change the unix socket address if it's different for you. # Change the unix socket address if it's different for you.
fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock; fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
fastcgi_index index.php; fastcgi_index index.php;
# Change this to `fastcgi_params` if you use a debian based distro. # Change this to `fastcgi_params` if you use a debian based distribution.
include fastcgi.conf; include fastcgi.conf;
fastcgi_intercept_errors on; fastcgi_intercept_errors on;
} }
@@ -34,56 +74,96 @@ server {
return 301 $1; return 301 $1;
} }
listen 80;
} }
``` ```
* The above is a very basic configuration and thus will need tweaking to your personal needs. It should still work as-is, though. A 'real world' example is present in [^2].
That is a very basic config so you will need to adapt it to your needs in case you have a more complicated nginx configuration. Anyways, you can see a real world example [here](https://git.zzls.xyz/Fijxu/etc-configs/src/branch/selfhost/nginx/sites-available/4get.zzls.xyz.conf) * After saving the file, check that the `nginx.conf` file inside the main directory includes files inside `conf.d/`:
* It should be inside the the http block: *(The following is an example! Don't just Copy and Paste it!)*
After you save the file you will need to do a symlink of the `4get.conf` file to `/etc/nignx/sites-enabled/`, you can do it with this command:
```sh ```sh
ln -s /etc/nginx/sites-available/4get.conf /etc/nginx/sites-available/4get.conf http {
include mime.types;
include conf.d/*.conf;
types_hash_max_size 4096;
# ...
}
``` ```
* Now, test your configuration with `nginx -t`, if it says that everything is good, restart *(or start)* the Nginx daemon:
* This depends on the init manager, most distributions use `systemd`, but it's better practice to include most.
```sh
# systemd
systemctl stop nginx
systemctl start nginxt
# or
systemctl restart nginx
Now test the nginx config with `nginx -t`, if it says that everything is good, restart nginx using `systemctl restart nginx` # openrc
rc-service nginx stop
rc-service nginx start
# or
rc-service nginx restart
# Encryption setup # runit
sv down nginx
sv up nginx
# or
sv restart nginx
Generate a certificate for the domain using: # s6
s6-rc -d change nginx
s6-rc -u change nginx
# or
s6-svc -r /run/service/nginx
# dinit
dinitctl stop nginx
dinitctl start nginx
# or
dinitctl restart nginx
```
6. Clone the repository to `/var/www`:
* `git clone --depth 1 https://git.lolcat.ca/lolcat/4get 4get` - It clones the repository with the depth of one commit *(so it takes less time to download)* and saves the cloned repository as '4get'.
7. That should be it! There are some extra steps you can take, but it really just depends on you.
<h2 align=center>Encryption setup</h2>
1. Generate a certificate for the domain you're using with:
* Note that `certbot-nginx` is needed.
```sh ```sh
certbot --nginx --key-type ecdsa -d www.yourdomain.com -d yourdomain.com certbot --nginx --key-type ecdsa -d www.yourdomain.com -d yourdomain.com
``` ```
(Remember to install the nginx certbot plugin!!!) 2. After that, certbot will deploy the certificate automatically to your 4get conf file; It should be ready to use from there.
After doing that certbot should deploy the certificate automatically into your 4get nginx config file. It should be ready to use at that point. <h2 align=center>Tor Setup</h2>
# Tor setup on NGINX <div align=right>
Important Note: Tor onion addresses are significantly longer than traditional domain names. Before proceeding with Nginx configuration, ensure you increase the `server_names_hash_bucket_size` value in your `nginx.conf` file. This setting in your Nginx configuration controls the internal data structure used to manage multiple server names (hostnames) associated with your web server. Each hostname requires a certain amount of memory within this structure. If the size is insufficient, Nginx will encounter errors. > IMPORTANT: Tor onion addresses are very long compared to traditional domains, so, Before doing anything, edit `nginx.conf` and increase <abbr title="This setting in your Nginx configuration controls the internal data structure used to manage multiple server names (hostnames) associated with your web server. Each hostname requires a certain amount of memory within this structure. If the size is insufficient, Nginx will encounter errors."><code>server_names_hash_bucket_size</code></abbr> to your needs.
1. Open your `nginx.conf` file (that is under `/etc/nginx/nginx.conf`). </div>
2. Find the line containing `# server_names_hash_bucket_size 64;`.
3. Uncomment the line and adjust the value. Start with 64, but if you encounter issues, incrementally increase it (e.g., 128, 256) until it accommodates your configuration.
Open your current 4get NGINX config (that is under `/etc/nginx/sites-available/`) and append this to the end of the file: 1. `cd` to `/etc/nginx` *(if you haven't)* and open your `nginx.conf` file.
2. Find the line containing `# server_names_hash_bucket_size 64;` inside said file.
``` 3. Uncomment the line and adjust the value; start with 64, but if you encounter issues, incrementally increase it *(e.g., 128, 256)* until it accommodates your configuration.
4. Open *(or duplicate the configuration)* and edit it:
* Example configuration, again:
```sh
server { server {
access_log /dev/null; access_log /dev/null; # Search log file. Do you really need to?
error_log /dev/null; error_log /dev/null; # Error log file.
listen 80; # Change this if you have 4get in another folder.
server_name <youronionaddress>;
root /var/www/4get; root /var/www/4get;
# Change 'onionadress.onion' to your onion link.
server_name onionadress.onion;
# Port to listen to.
listen 80;
location @php { location @php {
try_files $uri.php $uri/index.php =404; try_files $uri.php $uri/index.php =404;
# Change the unix socket address if it's different for you. # Change the unix socket address if it's different for you.
fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock; fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
fastcgi_index index.php; fastcgi_index index.php;
# Change this to `fastcgi_params` if you use a debian based distro. # Change this to `fastcgi_params` if you use a debian based distribution.
include fastcgi.conf; include fastcgi.conf;
fastcgi_intercept_errors on; fastcgi_intercept_errors on;
} }
@@ -95,9 +175,20 @@ server {
location ~* ^(.*)\.php$ { location ~* ^(.*)\.php$ {
return 301 $1; return 301 $1;
} }
} }
``` ```
A real world example is present in [^2].
5. Once done, check the configuration with `nginx -t`. If everything's fine and dandy, refer to <a href="https://git.lolcat.ca/lolcat/4get/src/branch/master/docs/tor.md">the Tor guide</a> to setup your onion site.
Obviously replace `<youronionaddress>` by the onion address of `/var/lib/tor/4get/hostname` and then check if the nginx config is valid with `nginx -t` if yes, then restart the nginx service and try opening the onion address into the Tor Browser. You can see a real world example [here](https://git.zzls.xyz/Fijxu/etc-configs/src/branch/selfhost/nginx/sites-available/4get.zzls.xyz.conf) <h2 align=center>Other important things</h2>
Once you did the above, refer to <a href="https://git.lolcat.ca/lolcat/4get/src/branch/master/docs/tor.md">this tor guide</a> to setup your onionsite. 1. <a href="https://git.lolcat.ca/lolcat/4get/src/branch/master/docs/configure.md">Configuration guide</a>: Things to do after setup.
2. <a href="https://git.lolcat.ca/lolcat/4get/src/branch/master/docs/apache2.md">Apache2 guide</a>: Fallback to this if you couldn't get something to work, or you don't know something.
<h2 align=center>Known issues</h2>
1. https://git.lolcat.ca/lolcat/4get/issues
[^1]: lolcat/4get#40, If having issues with `libsodium`, or `php-apcu`.
[^2]: <a href="https://git.nadeko.net/Fijxu/etc-configs/src/branch/selfhost/nginx/conf.d/4get.conf">git.nadeko.net</a> nadeko.net's 4get instance configuration.
+337
View File
@@ -0,0 +1,337 @@
const fplay = require("@lawlers/4play");
const http = require("http");
// 4play settings
var port = 3030;
var timeout = 30000;
var password = "cnc";
// ==================================================
// webserver
var global_ws = null;
const webserver_port = 3000;
var callback_q = new Map();
const server = http.createServer(async function(req, res){
const parsed = new URL(req.url, "http://localhost");
const url = parsed.searchParams.get("url");
const pw = parsed.searchParams.get("pw");
var proxy = parsed.searchParams.get("proxy");
const url_match = parsed.searchParams.get("url_match");
const content_match = parsed.searchParams.get("content_match");
const fail_url_match = parsed.searchParams.get("fail_url_match");
const fail_content_match = parsed.searchParams.get("fail_content_match");
var container = parsed.searchParams.get("container");
if(fplay.browser_connected === false){
res.writeHead(
400,
{
"Content-Type": "text/plain"
}
);
res.end("No browser available to render the motherfucking page");
return;
}
var errors = [];
if(
pw === null ||
pw != password
){
errors.push("pw");
}
if(url === null){ errors.push("url"); }
if(proxy === null){ errors.push("proxy"); }
if(errors.length !== 0){
res.writeHead(
400,
{
"Content-Type": "text/plain"
}
);
res.end("The following parameters are missing or invalid: " + errors.join(", "));
return;
}
console.log("Rendering " + url);
if(
container === null ||
await fplay.container_exists(global_ws, container) === false
){
container = await fplay.container_create(global_ws);
proxy = get_proxy(proxy);
// assign proxy
if(proxy.config !== false){
await fplay.container_attach_proxy(
global_ws,
container,
proxy.config
);
}
}else{
proxy = {raw: proxy};
}
// open tab
const newtab = await fplay.tab_open(global_ws, url, false, container);
if(newtab === false){
// failed to open tab
res.writeHead(
400,
{
"Content-Type": "text/plain",
"X-Proxy": proxy.raw,
"X-Container-ID": typeof container.id == "undefined" ? container : container.id
}
);
res.end("Failed to open tab");
return;
}
// callback will be handled in "web_response"
callback_q.set(
newtab.id,
{
http_res: res,
proxy: proxy,
container: container,
url_match: url_match,
content_match: content_match,
fail_url_match: fail_url_match,
fail_content_match: fail_content_match
}
);
// close tab in 30 seconds
await fplay.wait_random(15000, 35000);
await fplay.tab_close(global_ws, newtab);
});
server.listen(webserver_port, function(){
console.log("http server running");
});
function get_proxy(proxy_line){
parts = proxy_line.split(":");
// format: type:address:port:username:password
// PHP explode(":", $ip, 5) splits into max 5 parts
var type = parts[0];
const address = parts[1];
const port = parts[2];
const username = parts[3];
const password = parts[4];
var proxy_dns = true;
switch(type){
case "raw_ip":
case "ip":
// no proxy, use direct connection
return {
config: false,
raw: "raw_ip::::"
}
break;
case "socks5":
type = "socks";
proxy_dns = false;
break;
case "socks4a":
type = "socks4";
break;
case "socks4":
proxy_dns = false;
break;
case "socks5_hostname":
case "socks5h":
case "socks5a":
type = "socks";
break;
case "http":
case "https":
// do nothing
break;
default:
// invalid proxy provided
return {
config: false,
raw: "raw_ip::::"
}
}
return {
config: {
type: type,
host: address,
port: port,
username: username,
password: password,
proxyDNS: proxy_dns
},
raw: proxy_line
};
}
fplay.event.on("server_ready", function(){
console.log("listening on port " + port + " (timeout=" + timeout + ")");
});
fplay.event.on("browser_connect", async function(ws){
global_ws = ws;
const ua = await fplay.get_ua(ws);
console.log("New browser instance connected: " + ua);
await fplay.close_all_tabs(ws);
await fplay.delete_all_containers(ws);
fplay.web_response_whitelist(ws, ["main_frame"]);
// clean up
const blanktab = await fplay.close_all_tabs(ws);
await fplay.delete_all_containers(ws);
});
fplay.event.on("web_response", async function(page){
var callback_data = callback_q.get(page.id);
if(typeof callback_data == "undefined"){ return; }
var body = page.body.toString();
var url_match = callback_data.url_match !== null ? page.url.match(new RegExp(callback_data.url_match, "i")) : null;
var content_match = callback_data.content_match !== null ? body.match(new RegExp(callback_data.content_match, "i")) : null;
var fail_url_match = callback_data.fail_url_match !== null ? page.url.match(new RegExp(callback_data.fail_url_match, "i")) : null;
var fail_content_match = callback_data.fail_content_match !== null ? body.match(new RegExp(callback_data.fail_content_match, "i")) : null;
// handle negative response
if(
(
callback_data.fail_url_match !== null ||
callback_data.fail_content_match !== null
) &&
(
fail_url_match !== null ||
fail_content_match !== null
)
){
callback_data.http_res.writeHead(
400,
{
"Content-Type": "text/plain",
"X-Proxy": callback_data.proxy.raw,
"X-Container-ID": callback_data.container.id
}
);
callback_data.http_res.end("Reached fail state (matched fail_url_match or fail_content_match)");
// cleanup browser + callback
await fplay.tab_close(global_ws, page.id);
await fplay.container_delete(global_ws, callback_data.container);
callback_q.delete(page.id);
return;
}
// handle positive response
if(
(
callback_data.url_match === null &&
callback_data.content_match === null
) ||
(
callback_data.url_match !== null &&
callback_data.content_match !== null &&
url_match &&
content_match
) ||
(
callback_data.url_match === null &&
callback_data.content_match !== null &&
content_match
) ||
(
callback_data.url_match !== null &&
callback_data.content_match === null &&
url_match
)
){
callback_data.http_res.writeHead(
200,
{
"Content-Type": "text/plain;charset=UTF-8",
"X-Proxy": callback_data.proxy.raw,
"X-Container-ID": typeof callback_data.container.id == "undefined" ? callback_data.container : callback_data.container.id
}
);
callback_data.http_res.end(body);
callback_q.delete(page.id); // cleanup callback only
return;
}
});
fplay.event.on("dom_load_fail", async function(page){
var callback_data = callback_q.get(page.id);
if(typeof callback_data == "undefined"){ return; }
callback_q.delete(page.id);
callback_data.http_res.writeHead(
400,
{
"Content-Type": "text/plain",
"X-Proxy": callback_data.proxy.raw,
"X-Container-ID": typeof callback_data.container.id == "undefined" ? callback_data.container : callback_data.container.id
}
);
callback_data.http_res.end("Network error (" + page.error + ")");
// cleanup browser
await fplay.tab_close(global_ws, page.id);
await fplay.container_delete(global_ws, callback_data.container);
});
fplay.init(port, password, timeout);
+151
View File
@@ -0,0 +1,151 @@
<?php
$scrapers = [
"web" => [
"DuckDuckGo",
"Brave",
"Yandex",
"Google",
"Google API",
"Google CSE",
"Yahoo! JAPAN",
"Startpage",
"Yep",
"Mwmbl",
"Mojeek",
"Naver",
"Baidu",
"Cốc Cốc",
"Solofield",
"Marginalia",
"Purili",
"wiby"
],
"images" => [
"DuckDuckGo",
"Yandex",
"Brave",
"Google",
"Google API",
"Google CSE",
"Yahoo! JAPAN",
"Startpage",
"Naver",
"Baidu",
"Solofield",
"Pinterest",
"Flickr",
"Pexels",
"Pixabay",
"Unsplash",
"500px",
"VSCO",
"Imgur",
"FindThatMeme"
],
"videos" => [
"YouTube",
"Vimeo",
"Sepia Search",
"DuckDuckGo",
"Brave",
"Yandex",
"Google",
"Yahoo! JAPAN",
"Startpage",
"Naver",
"Baidu",
"Cốc Cốc",
"Purili",
"Solofield"
],
"news" => [
"DuckDuckGo",
"Brave",
"Google",
"Yahoo! JAPAN",
"Startpage",
"Baidu"
],
"music" => [
"SoundCloud",
"Swisscows"
],
"autocomplete" => [
"Brave",
"DuckDuckGo",
"Yandex",
"Google",
"Qwant",
"Marginalia",
"YouTube",
"SoundCloud",
"Startpage",
"Kagi"
]
];
$lines = [
0 => "",
1 => ""
];
// get longest scraper string
// also get list of scrapers with most entries
$biggest_category_len = 0;
foreach($scrapers as $category_name => $category){
$count = count($category);
if($count > $biggest_category_len){
$biggest_category_len = $count;
}
}
foreach($scrapers as $category_name => $category){
// get longest string of the category
$longest_len = mb_strlen($category_name);
foreach($category as $scraper){
$strlen = mb_strlen($scraper);
if($strlen > $longest_len){
$longest_len = $strlen;
}
}
// add header
$lines[0] .= mb_str_pad("| {$category_name}", $longest_len + 3);
$lines[1] .= "|" . str_repeat("-", $longest_len + 2);
for($i=2; $i<count($category) + 2; $i++){
if(!isset($lines[$i])){
$lines[$i] = "";
}
$lines[$i] .= mb_str_pad("| {$category[$i - 2]}", $longest_len + 3);
}
if($i - 2 < $biggest_category_len){
for($k=$i; $k<$biggest_category_len + 2; $k++){
if(!isset($lines[$k])){
$lines[$k] = "";
}
$lines[$k] .= "|" . str_repeat(" ", $longest_len + 2);
}
}
}
foreach($lines as $line){
echo $line . "|\n";
}
+6 -1
View File
@@ -15,7 +15,12 @@ class favicon{
header("Content-Type: image/png"); header("Content-Type: image/png");
if(substr_count($url, "/") !== 2){ if(
preg_match(
'/^https?:\/\/[A-Za-z0-9.-]+$/',
$url
) === 0
){
header("X-Error: Only provide the protocol and domain"); header("X-Error: Only provide the protocol and domain");
$this->defaulticon(); $this->defaulticon();
+3
View File
@@ -24,6 +24,9 @@ $payload = [
"nextpage" => "" "nextpage" => ""
]; ];
// increment request count
$frontend->increment_real_reqs($get["scraper"]);
try{ try{
$results = $scraper->image($get); $results = $scraper->image($get);
+103
View File
@@ -0,0 +1,103 @@
<?php
//
// Reference
// https://github.com/TecharoHQ/anubis/blob/ecc716940e34ebe7249974f2789a99a2c7115e4e/web/js/proof-of-work.mjs
//
class anubis{
public function __construct(){
include_once "fuckhtml.php";
$this->fuckhtml = new fuckhtml();
}
public function scrape($html){
$this->fuckhtml->load($html);
$script =
$this->fuckhtml
->getElementById(
"anubis_challenge",
"script"
);
if($script === false){
throw new Exception("Failed to scrape anubis challenge data");
}
$script =
json_decode(
$this->fuckhtml
->getTextContent(
$script
),
true
);
if($script === null){
throw new Exception("Failed to decode anubis challenge data");
}
if(
!isset($script["challenge"]) ||
!is_string($script["challenge"]["randomData"]) ||
!isset($script["rules"]["difficulty"]) ||
!is_int($script["rules"]["difficulty"])
){
throw new Exception("Found invalid challenge data");
}
return $this->rape($script["challenge"]["id"], $script["challenge"]["randomData"], $script["rules"]["difficulty"]);
}
private function is_valid_hash($hash, $difficulty){
for ($i=0; $i<$difficulty; $i++) {
$index = (int)floor($i / 2);
$nibble = $i % 2;
$byte = ord($hash[$index]);
$nibble = ($byte >> ($nibble === 0 ? 4 : 0)) & 0x0f;
if($nibble !== 0){
return false;
}
}
return true;
}
public function rape($id, $data, $difficulty = 5){
$nonce = 0;
$microtime = microtime(true);
while(true){
$hash_binary = hash("sha256", $data . $nonce, true);
if($this->is_valid_hash($hash_binary, $difficulty)){
$hash_hex = bin2hex($hash_binary);
return [
"id" => $id,
"response" => $hash_hex,
//"data" => $data,
//"difficulty" => $difficulty,
"nonce" => $nonce,
"elapsedTime" => round((microtime(true) - $microtime) * 1000)
];
}
$nonce++;
}
}
}
+162 -2
View File
@@ -4,14 +4,17 @@ class backend{
public function __construct($scraper){ public function __construct($scraper){
$this->scraper = $scraper; $this->scraper = $scraper;
$this->db = null;
$this->db_path_prefix = "data/sessions/";
$this->db_path = "{$this->db_path_prefix}{$scraper}.sqlite";
} }
/* /*
Proxy stuff Proxy stuff
*/ */
public function get_ip(){ public function get_ip($proxy_index_raw = null, $use_alt_list = false){
$pool = constant("config::PROXY_" . strtoupper($this->scraper)); $pool = constant("config::PROXY_" . strtoupper($this->scraper) . ($use_alt_list ? "_ALT" : ""));
if($pool === false){ if($pool === false){
// we don't want a proxy, fuck off! // we don't want a proxy, fuck off!
@@ -19,7 +22,10 @@ class backend{
} }
// indent // indent
if($proxy_index_raw === null){
$proxy_index_raw = apcu_inc("p." . $this->scraper); $proxy_index_raw = apcu_inc("p." . $this->scraper);
}
$proxylist = file_get_contents("data/proxies/" . $pool . ".txt"); $proxylist = file_get_contents("data/proxies/" . $pool . ".txt");
$proxylist = explode("\n", $proxylist); $proxylist = explode("\n", $proxylist);
@@ -32,6 +38,12 @@ class backend{
$proxylist = array_values($proxylist); $proxylist = array_values($proxylist);
if(count($proxylist) === 0){
throw new Exception("A proxy list was specified but it's empty!");
}
//echo $proxylist[$proxy_index_raw % count($proxylist)];
return $proxylist[$proxy_index_raw % count($proxylist)]; return $proxylist[$proxy_index_raw % count($proxylist)];
} }
@@ -75,6 +87,7 @@ class backend{
break; break;
case "socks5_hostname": case "socks5_hostname":
case "socks5h":
case "socks5a": case "socks5a":
curl_setopt($curlproc, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME); curl_setopt($curlproc, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME);
curl_setopt($curlproc, CURLOPT_PROXY, $address . ":" . $port); curl_setopt($curlproc, CURLOPT_PROXY, $address . ":" . $port);
@@ -87,6 +100,30 @@ class backend{
} }
} }
// API key rotation
public function get_key(){
$keys = file_get_contents("data/api_keys/" . $this->scraper . ".txt");
$keys = explode("\n", $keys);
$keys = array_filter($keys, function($entry){
$entry = ltrim($entry);
return strlen($entry) > 0 && substr($entry, 0, 1) != "#";
});
$keys = array_values($keys);
if(count($keys) === 0){
throw new Exception("Please specify API keys in data/api_keys/" . $this->scraper . ".txt");
}
$increment = apcu_inc("s." . $this->scraper) % count($keys);
return [
"key" => $keys[$increment],
"increment" => $increment
];
}
/* /*
@@ -174,4 +211,127 @@ class backend{
$payload[1] // proxy $payload[1] // proxy
]; ];
} }
//
// Sessions
//
public function session_init_db($force_init = false){
if(
$force_init ||
$this->db === null
){
$this->db = new SQLite3($this->db_path);
// create table if it doesnt exist
$this->db->exec(
"CREATE TABLE IF NOT EXISTS sessions ( " .
"id INTEGER PRIMARY KEY AUTOINCREMENT, " .
"created_at INTEGER NOT NULL, " .
"proxy TEXT NOT NULL, " .
"headers_json TEXT NOT NULL" .
")"
);
return true;
}
return false;
}
public function session_count(){
$this->session_init_db();
$stmt = $this->db->prepare(
"SELECT COUNT(*) as count FROM sessions"
);
$result = $stmt->execute();
$row = $result->fetchArray(SQLITE3_ASSOC);
return (int)($row["count"] ?? 0);
}
public function session_write($proxy, $headers){
$this->session_init_db();
$stmt = $this->db->prepare(
"INSERT INTO sessions ( " .
"created_at, " .
"proxy, " .
"headers_json " .
") VALUES ( " .
":created_at, " .
":proxy, " .
":headers_json" .
")"
);
$stmt->bindValue(':created_at', time(), SQLITE3_INTEGER);
$stmt->bindValue(':proxy', $proxy, SQLITE3_TEXT);
$stmt->bindValue(':headers_json', json_encode($headers), SQLITE3_TEXT);
$stmt->execute();
return $this->db->lastInsertRowID();
}
public function session_get(){
$count = $this->session_count();
if($count === 0){
return false;
}
$offset = apcu_inc("d." . $this->scraper) % $count;
$stmt = $this->db->prepare(
"SELECT * " .
"FROM sessions " .
"ORDER BY id ASC " .
"LIMIT 1 OFFSET :offset"
);
$stmt->bindValue(':offset', $offset, SQLITE3_INTEGER);
$result = $stmt->execute();
$row = $result->fetchArray(SQLITE3_ASSOC);
return [
"id" => $row["id"],
"created_at" => $row["created_at"],
"proxy" => $row["proxy"],
"headers" => json_decode($row["headers_json"], true)
];
}
public function session_destroy_list(){
if(
!preg_match(
'/^[A-Za-z0-9-]+$/',
$this->scraper
)
){
// path traversal
return false;
}
if(
file_exists("{$this->db_path_prefix}{$this->scraper}.sqlite") &&
is_file("{$this->db_path_prefix}{$this->scraper}.sqlite")
){
unlink("{$this->db_path_prefix}{$this->scraper}.sqlite");
return true;
}
return false;
}
} }
-144
View File
@@ -1,144 +0,0 @@
<?php
// https://www.bing.com/search?q=url%3Ahttps%3A%2F%2Flolcat.ca
// https://cc.bingj.com/cache.aspx?q=url%3ahttps%3a%2f%2flolcat.ca&d=4769685974291356&mkt=en-CA&setlang=en-US&w=tEsWuE7HW3Z5AIPQMVkDH4WaotS4LrK-
// <div class="b_attribution" u="0N|5119|4769685974291356|tEsWuE7HW3Z5AIPQMVkDH4WaotS4LrK-" tabindex="0">
new bingcache();
class bingcache{
public function __construct(){
if(
!isset($_GET["s"]) ||
$this->validate_url($_GET["s"]) === false
){
var_dump($this->validate_url($_GET["s"]));
$this->do404("Please provide a valid URL.");
}
$url = $_GET["s"];
$curlproc = curl_init();
curl_setopt(
$curlproc,
CURLOPT_URL,
"https://www.bing.com/search?q=url%3A" .
urlencode($url)
);
curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
curl_setopt(
$curlproc,
CURLOPT_HTTPHEADER,
["User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/107.0",
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip",
"DNT: 1",
"Connection: keep-alive",
"Upgrade-Insecure-Requests: 1",
"Sec-Fetch-Dest: document",
"Sec-Fetch-Mode: navigate",
"Sec-Fetch-Site: none",
"Sec-Fetch-User: ?1"]
);
curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 5);
$data = curl_exec($curlproc);
if(curl_errno($curlproc)){
$this->do404("Failed to connect to bing servers. Please try again later.");
}
curl_close($curlproc);
preg_match(
'/<div class="b_attribution" u="(.*)" tabindex="0">/',
$data,
$keys
);
print_r($keys);
if(count($keys) === 0){
$this->do404("Bing has not archived this URL.");
}
$keys = explode("|", $keys[1]);
$count = count($keys);
//header("Location: https://cc.bingj.com/cache.aspx?d=" . $keys[$count - 2] . "&w=" . $keys[$count - 1]);
echo("Location: https://cc.bingj.com/cache.aspx?d=" . $keys[$count - 2] . "&w=" . $keys[$count - 1]);
}
public function do404($text){
include "lib/frontend.php";
$frontend = new frontend();
echo
$frontend->load(
"error.html",
[
"title" => "Shit",
"text" => $text
]
);
die();
}
public function validate_url($url){
$url_parts = parse_url($url);
// check if required parts are there
if(
!isset($url_parts["scheme"]) ||
!(
$url_parts["scheme"] == "http" ||
$url_parts["scheme"] == "https"
) ||
!isset($url_parts["host"])
){
return false;
}
if(
// if its not an RFC-valid URL
!filter_var($url, FILTER_VALIDATE_URL)
){
return false;
}
$ip =
str_replace(
["[", "]"], // handle ipv6
"",
$url_parts["host"]
);
// if its not an IP
if(!filter_var($ip, FILTER_VALIDATE_IP)){
// resolve domain's IP
$ip = gethostbyname($url_parts["host"] . ".");
}
// check if its localhost
return filter_var(
$ip,
FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
);
}
}
+108 -6
View File
@@ -4,10 +4,96 @@ class bot_protection{
public function __construct($frontend, $get, $filters, $page, $output){ public function __construct($frontend, $get, $filters, $page, $output){
if(config::RATELIMIT_BURST !== 0){
// ratelimit requests per IP
if(config::RATELIMIT_IP_SOURCE === false){
$source = $_SERVER["REMOTE_ADDR"];
}else{
$source = $_SERVER["HTTP_" . explode(";", strtoupper(str_replace("-", "_", config::RATELIMIT_IP_SOURCE)))[0]];
}
// check if its not a local IP. Probably local tor bridge, dont ratelimit
if(
filter_var(
$source,
FILTER_VALIDATE_IP,
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
) !== false
){
$source = "r." . $source;
// counter
// resets on every req
$counter = apcu_fetch($source);
$counter = (int)$counter + 1;
if(
(
CONFIG::RATELIMIT_PUNISH !== 0 &&
$counter > config::RATELIMIT_BURST + 1
) &&
(
(
$output === false &&
CONFIG::RATELIMIT_PUNISH_API === true
) ||
$output === true
)
){
$block_time = config::RATELIMIT_TIME + (($counter - 1) * config::RATELIMIT_PUNISH);
}else{
$block_time = config::RATELIMIT_TIME;
}
apcu_store($source, $counter, $block_time);
if($counter > config::RATELIMIT_BURST){
apcu_inc(intdiv(time(), 3600) . ".bot_requests", 1, $s, 262800);
if($output === false){
http_response_code(429);
echo json_encode([
"status" => "You love 4get too much. Try again in " . $this->format_time($block_time) . "."
]);
die();
}
$payload = [
"timetaken" => microtime(true),
"class" => "",
"right-left" => "",
"right-right" => "",
"left" =>
'<div class="infobox">' .
'<h1>You\'re damaging my calm</h1>' .
'You love 4get too much. Try again in <b>' . $this->format_time($block_time) . '</b>.' .
'</div>'
];
$frontend->loadheader(
$get,
$filters,
$page,
false
);
echo $frontend->load("search.html", $payload);
die();
}
}
}
// check if we want captcha // check if we want captcha
if(config::BOT_PROTECTION !== 1){ if(config::BOT_PROTECTION !== 1){
apcu_inc("real_requests");
if($output === true){ if($output === true){
$frontend->loadheader( $frontend->loadheader(
$get, $get,
@@ -46,8 +132,6 @@ class bot_protection{
}else{ }else{
// the cookie is OK! dont die() and give results // the cookie is OK! dont die() and give results
apcu_inc("real_requests");
if($output === true){ if($output === true){
$frontend->loadheader( $frontend->loadheader(
$get, $get,
@@ -133,6 +217,9 @@ class bot_protection{
$answers[] = $regex; $answers[] = $regex;
} }
// dedup
$answers = array_unique($answers);
if( if(
!$invalid && !$invalid &&
$key !== false // has captcha been gen'd? $key !== false // has captcha been gen'd?
@@ -161,8 +248,7 @@ class bot_protection{
$key = "k" . $inc . "." . $this->randomchars(); $key = "k" . $inc . "." . $this->randomchars();
apcu_inc($key, 1, $stupid, 86400); apcu_inc($key, 1, $stupid, 86400);
apcu_dec(intdiv(time(), 3600) . ".bot_requests", 1, $s, 262800);
apcu_inc("real_requests");
setcookie( setcookie(
"pass", "pass",
@@ -187,6 +273,7 @@ class bot_protection{
} }
} }
apcu_inc(intdiv(time(), 3600) . ".bot_requests", 1, $s, 262800);
$key = "c" . apcu_inc("captcha_gen", 1) . "." . $this->randomchars(); $key = "c" . apcu_inc("captcha_gen", 1) . "." . $this->randomchars();
$payload = [ $payload = [
@@ -249,13 +336,28 @@ class bot_protection{
$frontend->loadheader( $frontend->loadheader(
$get, $get,
$filters, $filters,
$page $page,
false
); );
echo $frontend->load("search.html", $payload); echo $frontend->load("search.html", $payload);
die(); die();
} }
private function format_time($seconds){
$hours = floor($seconds / 3600);
$minutes = floor(($seconds % 3600) / 60);
$secs = $seconds % 60;
if($hours > 0){
return sprintf('%dh%02dm%02ds', $hours, $minutes, $secs);
}
return sprintf('%dm%02ds', $minutes, $secs);
}
private function randomchars(){ private function randomchars(){
$chars = $chars =
+625 -389
View File
File diff suppressed because it is too large. Load diff
+131 -4
View File
@@ -13,7 +13,7 @@ class fuckhtml{
if(is_array($html)){ if(is_array($html)){
if(!isset($html["innerHTML"])){ if(!array_key_exists("innerHTML", $html)){
throw new Exception("(load) Supplied array doesn't contain an innerHTML index"); throw new Exception("(load) Supplied array doesn't contain an innerHTML index");
} }
@@ -240,12 +240,13 @@ class fuckhtml{
public function getElementsByFuzzyAttributeValue(string $name, string $value, $collection = null){ public function getElementsByFuzzyAttributeValue(string $name, string $value, $collection = null){
$elems = $this->getElementsByAttributeName($name, $collection); $elems = $this->getElementsByAttributeName($name, $collection);
$value = $value =
explode( explode(
" ", " ",
trim( trim(
preg_replace( preg_replace(
'/ +/', '/\s+/',
" ", " ",
$value $value
) )
@@ -258,7 +259,18 @@ class fuckhtml{
foreach($elem["attributes"] as $attrib_name => $attrib_value){ foreach($elem["attributes"] as $attrib_name => $attrib_value){
$attrib_value = explode(" ", $attrib_value); $attrib_value =
explode(
" ",
trim(
preg_replace(
'/\s+/',
" ",
$attrib_value
)
)
);
$ac = count($attrib_value); $ac = count($attrib_value);
$nc = count($value); $nc = count($value);
$cr = 0; $cr = 0;
@@ -327,7 +339,7 @@ class fuckhtml{
if(is_array($html)){ if(is_array($html)){
if(!isset($html["innerHTML"])){ if(!array_key_exists("innerHTML", $html)){
throw new Exception("(getTextContent) Supplied array doesn't contain an innerHTML index"); throw new Exception("(getTextContent) Supplied array doesn't contain an innerHTML index");
} }
@@ -381,6 +393,8 @@ class fuckhtml{
$json_out = null; $json_out = null;
$last_char = null; $last_char = null;
$keyword_check = null;
for($i=0; $i<strlen($json); $i++){ for($i=0; $i<strlen($json); $i++){
switch($json[$i]){ switch($json[$i]){
@@ -396,6 +410,7 @@ class fuckhtml{
$bracket = false; $bracket = false;
$is_close_bracket = true; $is_close_bracket = true;
}else{ }else{
if($bracket === false){ if($bracket === false){
@@ -429,6 +444,31 @@ class fuckhtml{
$is_close_bracket === false $is_close_bracket === false
){ ){
// do keyword check
$keyword_check .= $json[$i];
if(in_array($json[$i], [":", "{"])){
$keyword_check = substr($keyword_check, 0, -1);
if(
preg_match(
'/function|array|return/i',
$keyword_check
)
){
$json_out =
preg_replace(
'/[{"]*' . preg_quote($keyword_check, "/") . '$/',
"",
$json_out
);
}
$keyword_check = null;
}
// here we know we're not iterating over a quoted string // here we know we're not iterating over a quoted string
switch($json[$i]){ switch($json[$i]){
@@ -498,4 +538,91 @@ class fuckhtml{
$string $string
); );
} }
public function extract_json($json){
$len = strlen($json);
$array_level = 0;
$object_level = 0;
$in_quote = null;
$start = null;
for($i=0; $i<$len; $i++){
switch($json[$i]){
case "\"":
case "'":
// count preceding backslashes
$bsCount = 0;
$j = $i - 1;
while($j >= 0 && $json[$j] === "\\"){
$bsCount++;
$j--;
}
// quote is NOT escaped if even number of backslashes
if($bsCount % 2 === 0){
if($in_quote === null){
// open quote
$in_quote = $json[$i];
}elseif($in_quote === $json[$i]){
// close quote
$in_quote = null;
}
}
break;
case "[":
if($in_quote === null){
$array_level++;
if($start === null){
$start = $i;
}
}
break;
case "]":
if($in_quote === null){
$array_level--;
}
break;
case "{":
if($in_quote === null){
$object_level++;
if($start === null){
$start = $i;
}
}
break;
case "}":
if($in_quote === null){
$object_level--;
}
break;
}
if(
$array_level === 0 &&
$object_level === 0 &&
$start !== null
){
return substr($json, $start, $i - $start + 1);
break;
}
}
// fallback
return "[]";
}
} }
+388
View File
@@ -0,0 +1,388 @@
<?php
class graph{
public function render(array $graph = [], array $size = [], array $colors = [], array $colors_line = []){
$graph = array_merge([
"title" => "Title",
"subtitle" => "description",
"grad_x" => 25,
"grad_y" => 20,
"x_label" => "<= x =>",
"y_label" => "<= y =>",
"antialias" => false,
"data" => []
], $graph);
$size = array_merge([
"image_width" => 750,
"image_height" => 500,
"x" => 90,
"y" => 50,
"width" => 650,
"height" => 300,
"square" => 11,
"column" => 6
], $size);
$colors = array_merge([
"bg" => new ImagickPixel("#1d2021"),
"lines_bg" => new ImagickPixel("#3c3836"),
"outline" => new ImagickPixel("#a89984"),
"text" => new ImagickPixel("#ebdbb2"),
"transparent" => new ImagickPixel("transparent")
], $colors);
$colors_line = array_merge([
new ImagickPixel("#83a598"),
new ImagickPixel("#fb4934"),
new ImagickPixel("#b8bb26"),
new ImagickPixel("#fabd2f"),
new ImagickPixel("#d3869b"),
new ImagickPixel("#8ec07c"),
new ImagickPixel("#fe8019"),
new ImagickPixel("#458588"),
new ImagickPixel("#cc241d"),
new ImagickPixel("#98971a"),
new ImagickPixel("#d79921"),
new ImagickPixel("#b16286"),
new ImagickPixel("#689d6a"),
new ImagickPixel("#d65d0e"),
new ImagickPixel("#a89984") // other
], $colors_line);
$not_enough_data = false;
// compute datapoint max ranges
$max_range_x = 0;
$max_range_y = 0;
if(count($graph["data"]) === 0){
$not_enough_data = true;
}else{
foreach($graph["data"] as $datapoint => $value){
if(count($value) < 2){
$not_enough_data = true;
}else{
$nv1 = [];
$nv2 = [];
foreach($value as $k => $v){
$nv1[] = $v[0];
$nv2[] = $v[1];
}
$max1 = max($nv1);
$max2 = max($nv2);
if($max_range_x < $max1){ $max_range_x = $max1; }
if($max_range_y < $max2){ $max_range_y = $max2; }
}
}
}
$im = new Imagick();
$im->newImage($size["image_width"], $size["image_height"], $colors["bg"]);
$im->setImageFormat("png");
$draw = new ImagickDraw();
//
// Draw background grid
//
$draw->setStrokeColor($colors["lines_bg"]);
$draw->setStrokeAntialias($graph["antialias"]);
$draw->setStrokeWidth(1);
$draw->setFillColor($colors["transparent"]);
$text = new ImagickDraw();
$text->setFontSize(11);
$text->setTextAntialias($graph["antialias"]);
$text->setFillColor($colors["text"]);
// vertical lines
$offset = $size["width"] / ($graph["grad_x"] - 1);
for($i=0; $i<$graph["grad_x"]; $i++){
$x = round($size["x"] + ($offset * $i));
if(
$i === 0 ||
$i === $graph["grad_x"] - 1
){
$draw->setStrokeColor($colors["outline"]);
}else{
$draw->setStrokeColor($colors["lines_bg"]);
}
$draw->line(
$x,
$size["y"],
$x,
$size["y"] + $size["height"],
);
if($not_enough_data === false){
// add numbas
$number = number_format(round(($graph["grad_x"] - 1 - $i) * ($max_range_x / ($graph["grad_x"] - 1))));
$number_width = $im->queryFontMetrics($text, $number)["textWidth"];
$im->annotateImage(
$text,
round($x - ($number_width / 2)),
$size["y"] + $size["height"] + 14,
0,
$number
);
}
}
// horizontal lines
$text->setTextAlignment(Imagick::ALIGN_RIGHT);
$offset = $size["height"] / ($graph["grad_y"] - 1);
$vertical_text_size = 0;
for($i=0; $i<$graph["grad_y"]; $i++){
$y = round($size["y"] + ($offset * $i));
if(
$i === 0 ||
$i === $graph["grad_y"] - 1
){
$draw->setStrokeColor($colors["outline"]);
}else{
$draw->setStrokeColor($colors["lines_bg"]);
}
$draw->line(
$size["x"] + 1,
$y,
$size["x"] + $size["width"] - 1,
$y,
);
if($not_enough_data === false){
$val = number_format(round(($graph["grad_y"] - 1 - $i) * ($max_range_y / ($graph["grad_y"] - 1))));
$query = $im->queryFontMetrics($text, $val);
if($query["textWidth"] > $vertical_text_size){
$vertical_text_size = $query["textWidth"];
}
// add numbas
$im->annotateImage(
$text,
$size["x"] - 4,
$y + 4,
0,
$val
);
}
}
//
// Draw legend
//
$text->setTextAlignment(Imagick::ALIGN_LEFT);
$top_padding = 44;
$c = count($colors_line);
$padding = $size["width"] / $size["column"];
$line = new ImagickDraw();
//$line->setStrokeWidth(1);
$line->setStrokeAntialias($graph["antialias"]);
if($not_enough_data === false){
$i = 0;
foreach($graph["data"] as $datafield => $data){
$used_color = $colors_line[$i % $c];
$draw->setStrokeColor($used_color);
$row = floor($i / $size["column"]);
$x = round($size["x"] + (($i % $size["column"]) * $padding));
$y = $size["y"] + $size["height"] + $top_padding + ($row * 17);
// draw legend
$draw->rectangle(
$x,
$y,
$x + $size["square"],
$y + $size["square"]
);
$im->annotateImage(
$text,
$x + $size["square"] + 4,
$y + 10,
0,
$datafield
);
//
// Draw lines
//
$line->setStrokeColor($used_color);
$old_x = null;
$old_y = null;
$continue = true;
foreach($data as $field){
// field 0 = hour (sorted)
// field 1 = datapoint
$x = $size["x"] + (($max_range_x - $field[0]) * ($size["width"] / $max_range_x));
$y = $size["y"] + (($max_range_y - $field[1]) * ($size["height"] / $max_range_y));
if($continue){
$continue = false;
$old_x = $x;
$old_y = $y;
continue;
}
$line->line($old_x, $old_y, $x, $y);
$old_x = $x;
$old_y = $y;
}
$i++;
}
}
//
// add background labels
//
$text->setFontSize(13);
$text->setFillColor($colors["bg"]);
$draw->setFillColor($colors["text"]);
$draw->setStrokeColor($colors["transparent"]);
// horizontal
$label_width_x = $im->queryFontMetrics($text, $graph["x_label"]);
$x_x = $size["x"] + ($size["width"] / 2) - ($label_width_x["textWidth"] / 2);
$x_y = $size["y"] + $size["height"] + ($not_enough_data ? 21 : 30);
$draw->rectangle(
$x_x - 2,
$x_y - $label_width_x["textHeight"] + 4,
$x_x + $label_width_x["textWidth"] + 2,
$x_y + 3,
);
// vertical
$label_width_y = $im->queryFontMetrics($text, $graph["y_label"]);
$y_x = $size["x"] - $label_width_y["textHeight"] - 13 - $vertical_text_size;
$y_y = $size["y"] + ($size["height"] / 2) - ($label_width_y["textWidth"] / 2);
$draw->rectangle(
$y_x + 4,
$y_y - 2,
$y_x + $label_width_y["textHeight"] + 3,
$y_y + $label_width_y["textWidth"] + 2,
);
$im->drawImage($draw);
$im->drawImage($line);
// horizontal text
$im->annotateImage(
$text,
$x_x,
$x_y,
0,
$graph["x_label"]
);
// vertical text
$im->annotateImage(
$text,
$y_x + $label_width_y["textHeight"],
$y_y + $label_width_y["textWidth"],
270,
$graph["y_label"]
);
//
// draw title + subtext
//
$text->setFontSize(20);
$text->setFontWeight(900);
$text->setFillColor($colors["text"]);
$im->annotateImage(
$text,
$size["x"],
$size["y"] - 25,
0,
$graph["title"]
);
$text->setFontSize(13);
$text->setFontWeight(100);
$im->annotateImage(
$text,
$size["x"],
$size["y"] - 10,
0,
$graph["subtitle"]
);
if($not_enough_data){
// add not enough data notice
$rect = new ImagickDraw();
$rect->setFillColor($colors["bg"]);
$rect->setStrokeColor($colors_line[1]);
$rect_w = 200;
$rect_h = 24;
$rect_x = round($size["x"] + ($size["width"] / 2) - ($rect_w / 2));
$rect_y = round($size["y"] + ($size["height"] / 2) - ($rect_h / 2));
$rect->rectangle(
$rect_x,
$rect_y,
$rect_x + $rect_w,
$rect_y + $rect_h
);
$im->drawImage($rect);
$label = "Not enough data";
$text->setFontWeight(900);
$query = $im->queryFontMetrics($text, $label);
$im->annotateImage(
$text,
round($size["x"] + ($size["width"] / 2) - ($query["textWidth"] / 2)),
round($size["y"] + ($size["height"] / 2) + ($query["textHeight"] / 2)) - 3,
0,
$label
);
}
return $im->getImageBlob();
}
}
+3
View File
@@ -26,6 +26,9 @@ $payload = [
"left" => "" "left" => ""
]; ];
// increment request count
$frontend->increment_real_reqs($get["scraper"]);
try{ try{
$results = $scraper->music($get); $results = $scraper->music($get);
+3
View File
@@ -26,6 +26,9 @@ $payload = [
"left" => "" "left" => ""
]; ];
// increment request count
$frontend->increment_real_reqs($get["scraper"]);
try{ try{
$results = $scraper->news($get); $results = $scraper->news($get);
-36
View File
@@ -1,36 +0,0 @@
<?php
abstract class oracle {
// some info to spit out alongside the result, so the user knows
// what exactly is giving out the answer. prevents confusion
// about what oracle is answering them for ambiguous queries.
public $info = [
"name" => "some oracle"
];
// this function should take in a query string search from $_GET,
// and return a bool determining whether or not it is a question
// intended for the oracle.
public function check_query($q) {
return false;
}
// produce the correct answer for the query using the oracle.
// note: if it becomes apparent /during generation/ that the
// query is not in fact for the oracle, returning an empty
// string will kill the oracle pane.
// answer format: ["ans1 title" => "ans1", ...]
public function generate_response($q) {
return "";
}
}
// backwards compatibility
if (!function_exists('str_starts_with')) {
function str_starts_with($haystack, $needle) {
return strncmp($haystack, $needle, strlen($needle)) === 0;;
}
}
if (!function_exists('str_contains')) {
function str_contains($haystack, $needle) {
return strpos((string)$haystack, (string)$needle) !== false;
}
}
?>
-165
View File
@@ -1,165 +0,0 @@
<?php
include_once("oracles/base.php");
class calculator extends oracle {
public $info = [
"name" => "calculator"
];
public function check_query($q) {
// straight numerics should go to that oracle
if (is_numeric($q)) {
return false;
}
// all chars should be number-y or operator-y
$char_whitelist = str_split("1234567890.+-/*^%() ");
foreach (str_split($q) as $char) {
if (!in_array($char, $char_whitelist)) {
return false;
}
}
return true;
}
// a custom parser and calculator because FUCK YUO, libraries are
// gay.
public function generate_response($q)
{
$nums = str_split("1234567890.");
$ops = str_split("+-/*^%;");
$grouping = str_split("()");
$q = str_replace(" ", "", $q);
// backstop for the parser so it catches the last
// numeric token
$q .= ";";
// the following comments refer to this example input:
// 21+9*(3+2^9)+1
// 2-length lists of the following patterns:
// ["n" (umeric), <some number>]
// ["o" (perator), "<some operator>"]
// ["g" (roup explicit), <"(" or ")">]
// e.g. [["n", 21], ["o", "+"], ["n", 9], ["o", *],
// ["g", "("], ["n", 3], ["o", "+"], ["n", 2],
// ["o", "^"], ["n", 9], ["g", ")"], ["o", "+"],
// ["n", "1"]]
$tokens = array();
$dragline = 0;
foreach(str_split($q) as $i=>$char) {
if (in_array($char, $nums)) {
continue;
}
elseif (in_array($char, $ops) || in_array($char, $grouping)) {
// hitting a non-numeric implies everything since the
// last hit has been part of a number
$capture = substr($q, $dragline, $i - $dragline);
// prevent the int cast from creating imaginary
// ["n", 0] tokens
if ($capture != "") {
if (substr_count($capture, ".") > 1) {
return "";
}
array_push($tokens, ["n", (float)$capture]);
}
// reset to one past the current (non-numeric) char
$dragline = $i + 1;
// the `;' backstop is not a real token and this should
// never be present in the token list
if ($char != ";") {
array_push($tokens, [
($char == "(" || $char == ")") ? "g" : "o",
$char
]);
}
}
else {
return "";
}
}
// two operators back to back should fail
for ($i = 1; $i < count($tokens); $i++) {
if ($tokens[$i][0] == "o" && $tokens[$i-1][0] == "o") {
return "";
}
}
// no implicit multiplication
for ($i = 0; $i < count($tokens) - 1; $i++) {
if ($tokens[$i][0] == "n" && $tokens[$i+1] == ["g", "("]) {
return "";
}
}
//strategy:
// traverse to group open (if there is one)
// - return to start with the internals
// traverse to ^, attack token previous and after
// same but for *, then / then + then -
// poppers all teh way down
try {
return [
substr($q, 0, strlen($q)-1)." = " => $this->executeBlock($tokens)[0][1]
];
}
catch (\Throwable $e) {
if (get_class($e) == "DivisionByZeroError") {
return [
$q." = " => "Division by Zero Error!!"
];
}
return "";
}
}
public function executeBlock($tokens) {
if (count($tokens) >= 2 && $tokens[0][0] == "o" && $tokens[0][1] == "-" && $tokens[1][0] == "n") {
array_splice($tokens, 0, 2, [["n", -1 * (float)$tokens[1][1]]]);
}
if (count($tokens) > 0 && $tokens[0][0] == "o" || $tokens[count($tokens)-1][0] == "o") {
throw new Exception("Error Processing Request", 1);
}
while (in_array(["g", "("], $tokens)) {
$first_open = array_search(["g", "("], $tokens);
$enclosedality = 1;
for ($i = $first_open+1; $i < count($tokens); $i++) {
if ($tokens[$i][0] == "g") {
$enclosedality += ($tokens[$i][1] == "(") ? 1 : -1;
}
if ($enclosedality == 0) {
array_splice($tokens,
$first_open,
$i+1 - $first_open,
$this->executeBlock(
array_slice($tokens, $first_open+1, $i-1 - $first_open)
)
);
break;
}
}
}
$operators_in_pemdas_order = [
"^" => (fn($x, $y) => $x ** $y),
"*" => (fn($x, $y) => $x * $y),
"/" => (fn($x, $y) => $x / $y),
"%" => (fn($x, $y) => $x % $y),
"+" => (fn($x, $y) => $x + $y),
"-" => (fn($x, $y) => $x - $y)
];
foreach ($operators_in_pemdas_order as $op=>$func) {
while (in_array(["o", $op], $tokens)) {
for ($i = 0; $i < count($tokens); $i++) {
if ($tokens[$i] == ["o", $op]) {
array_splice(
$tokens,
$i-1,
3,
[["n", (string)($func((float)$tokens[$i-1][1], (float)$tokens[$i+1][1]))]]
);
}
}
}
}
return $tokens;
}
}
?>
-40
View File
@@ -1,40 +0,0 @@
<?php
include_once("oracles/base.php");
class encoder extends oracle {
public $info = [
"name" => "text encoder/hasher"
];
private $special_types = [
"rot13",
"base64"
];
public function check_query($q) {
$types = array_merge($this->special_types, hash_algos());
foreach ($types as $type) {
$type .= " ";
if (str_starts_with($q, $type)) {
return true;
}
}
return false;
}
public function generate_response($q)
{
$type = explode(" ", $q)[0];
$victim = substr($q, strlen($type)+1);
if (in_array($type, hash_algos())) {
return [$type." hash" => hash($type, $victim)];
}
switch ($type) {
case "rot13":
return ["rot13 encoded" => str_rot13($victim)];
case "base64":
return [
"base64 encoded" => base64_encode($victim),
"base64 decoded" => base64_decode($victim)
];
}
return "";
}
}
?>
-54
View File
@@ -1,54 +0,0 @@
<?php
include_once("oracles/base.php");
class numerics extends oracle {
public $info = [
"name" => "numeric base conversion"
];
public function check_query($q) {
if (str_contains($q, " ")) {
return false;
}
$q = strtolower($q);
$profiles = [
["0x", str_split("0123456789abcdef")],
["", str_split("1234567890")],
["b", str_split("10")]
];
foreach ($profiles as $profile) {
$good = true;
$good &= str_starts_with($q, $profile[0]);
$nq = substr($q, strlen($profile[0]));
foreach (str_split($nq) as $c) {
$good &= in_array($c, $profile[1]);
}
if ($good) {
return true;
}
}
return false;
}
public function generate_response($q) {
$n = 0;
if (str_starts_with($q, "0x")) {
$nq = substr($q, strlen("0x"));
$n = hexdec($nq);
}
elseif (str_starts_with($q, "b")) {
$nq = substr($q, strlen("b"));
$n = bindec($nq);
}
else {
$n = (int)$q;
}
return [
"decimal (base 10)" => (string)$n,
"hexadecimal (base 16)" => "0x".(string)dechex($n),
"binary (base 2)" => "b".(string)decbin($n),
"" => "binary inputs should be prefixed with 'b', hex with '0x'."
];
}
}
?>
-45
View File
@@ -1,45 +0,0 @@
<?php
include_once("oracles/base.php");
class time extends oracle {
public $info = [
"name" => "what time is it?"
];
public function check_query($q) {
$prompts = [
"what", "time", "is", "it",
"right", "now", "the", "current",
"get", "date"
];
$q = str_replace(",", "", $q);
$q = str_replace("?", "", $q);
$q = str_replace("what's", "what is", $q);
$oq = $q;
$q = explode(" ", $q);
$count = 0;
foreach ($q as $word) {
if (in_array($word, $prompts)) {
$count++;
}
}
// remove one from total count if a timezone is specified
return ($count/(count($q) + (str_contains($oq, "tz:") ? -1 : 0))) > 3/4;
}
public function generate_response($q) {
$timezone = timezone_name_from_abbr("UTC");
foreach (explode(" ", $q) as $word) {
if (str_starts_with($word, "tz:")) {
$decltz = timezone_name_from_abbr(substr($word, 3, 3));
if ($decltz) {
$timezone = $decltz;
}
}
}
date_default_timezone_set($timezone);
return [
"The time in ".$timezone => date("H:i:s"),
" " => date("l, F jS"),
"" => "include the string \"tz:XXX\" to use timezone XXX"
];
}
}
?>
+33 -9
View File
@@ -34,22 +34,46 @@ try{
) )
){ ){
if( if(!isset($image["path"])){
!isset($image["query"]) ||
!isset($image["path"]) ||
$image["path"] != "/th"
){
header("X-Error: Invalid bing image path"); header("X-Error: Missing bing image path");
$proxy->do404(); $proxy->do404();
die(); die();
} }
//
// get image ID
// formations:
// https://tse2.mm.bing.net/th/id/OIP.3yLBkUPn8EXA1wlhWP2BHwHaE3
// https://tse2.mm.bing.net/th?id=OIP.3yLBkUPn8EXA1wlhWP2BHwHaE3
//
$id = null;
if(isset($image["query"])){
parse_str($image["query"], $str); parse_str($image["query"], $str);
if(!isset($str["id"])){ if(isset($str["id"])){
header("X-Error: Missing bing ID"); $id = $str["id"];
}
}
if($id === null){
$id = explode("/th/id/", $image["path"], 2);
if(count($id) !== 2){
// malformed
return $url;
}
$id = $id[1];
}
if(is_array($id)){
header("X-Error: Missing bing id parameter");
$proxy->do404(); $proxy->do404();
die(); die();
} }
@@ -63,7 +87,7 @@ try{
case "cover": $req = "&w=207&h=270&p=0&qlt=90"; break; case "cover": $req = "&w=207&h=270&p=0&qlt=90"; break;
} }
$proxy->stream_linear_image("https://" . $image["host"] . "/th?id=" . urlencode($str["id"]) . $req, "https://www.bing.com"); $proxy->stream_linear_image("https://" . $image["host"] . "/th?id=" . rawurlencode($id) . $req, "https://www.bing.com");
die(); die();
} }
+107
View File
@@ -0,0 +1,107 @@
<?php
class google{
public function __construct(){
include "lib/backend.php";
$this->backend = new backend("google");
include "lib/fuckhtml.php";
$this->fuckhtml = new fuckhtml();
}
private function get(/*$proxy, */$url, $get = []){
$curlproc = curl_init();
if($get !== []){
$get = http_build_query($get);
$url .= "?" . $get;
}
curl_setopt($curlproc, CURLOPT_URL, $url);
// http2 bypass
curl_setopt($curlproc, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
$headers =
["User-Agent: " . config::USER_AGENT,
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip, deflate, br, zstd",
"DNT: 1",
"Sec-GPC: 1",
"Connection: keep-alive",
"Upgrade-Insecure-Requests: 1",
"Sec-Fetch-Dest: document",
"Sec-Fetch-Mode: navigate",
"Sec-Fetch-Site: none",
"Sec-Fetch-User: ?1",
"Priority: u=0, i"];
// i dont think proxies are necessary here...
//$this->backend->assign_proxy($curlproc, $proxy);
curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
curl_setopt($curlproc, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
$data = curl_exec($curlproc);
if(curl_errno($curlproc)){
throw new Exception(curl_error($curlproc));
}
curl_close($curlproc);
return $data;
}
function resolve($id){
$html =
$this->get(
//$this->backend->get_ip(),
"https://www.google.com/goto",
[
"url" => $id
]
);
$this->fuckhtml->load($html);
$a =
$this->fuckhtml
->getElementsByTagName(
"a"
);
if(count($a) === 0){
throw new Exception("Failed to find link");
}
$link =
$this->fuckhtml
->getTextContent(
$a[0]["attributes"]["href"]
);
if(
preg_match(
'/^(https?:|)\/\//',
$link
)
){
return $link;
}
throw new Exception("Got malformed link: " . $link);
}
}
+117
View File
@@ -0,0 +1,117 @@
<?php
class sc{
public function __construct(){
include "lib/backend.php";
$this->backend = new backend("sc");
include "lib/fuckhtml.php";
$this->fuckhtml = new fuckhtml();
}
private function get($proxy, $url, $get = []){
$curlproc = curl_init();
if($get !== []){
$get = http_build_query($get);
$url .= "?" . $get;
}
curl_setopt($curlproc, CURLOPT_URL, $url);
// http2 bypass
curl_setopt($curlproc, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
$headers =
["User-Agent: " . config::USER_AGENT,
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip",
"DNT: 1",
"Sec-GPC: 1",
"Connection: keep-alive",
"Upgrade-Insecure-Requests: 1",
"Sec-Fetch-Dest: document",
"Sec-Fetch-Mode: navigate",
"Sec-Fetch-Site: none",
"Sec-Fetch-User: ?1",
"Priority: u=0, i"];
$this->backend->assign_proxy($curlproc, $proxy);
curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
curl_setopt($curlproc, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
$data = curl_exec($curlproc);
if(curl_errno($curlproc)){
throw new Exception(curl_error($curlproc));
}
curl_close($curlproc);
return $data;
}
function resolve($id){
if(
!preg_match(
'/^(t|p)[0-9]+$/',
$id
)
){
throw new Exception("ID is invalid");
}
$type = $id[0] == "t" ? "track" : "playlist";
$id = substr($id, 1);
try{
$html =
$this->get(
$this->backend->get_ip(),
"https://w.soundcloud.com/player/",
[
"url" => "http://api.soundcloud.com/{$type}s/{$id}",
"show_artwork" => "true"
]
);
}catch(Exception $error){
throw new Exception("Failed to fetch song embed page");
}
$this->fuckhtml->load($html);
$link =
$this->fuckhtml
->getElementsByAttributeValue(
"rel",
"canonical",
"link"
);
if(count($link) === 0){
throw new Exception("Soundcloud could not resolve the song ID to an URL");
}
return
$this->fuckhtml
->getTextContent(
$link[0]
["attributes"]
["href"]
);
}
}
+71
View File
@@ -0,0 +1,71 @@
<?php
$resolver = new resolver();
class resolver{
public const resolvers = [
"sc",
"google"
];
public function __construct(){
include "data/config.php";
if(
!isset($_GET["scraper"]) ||
!is_string($_GET["scraper"]) ||
!in_array($_GET["scraper"], self::resolvers)
){
$this->do400("Missing or invalid scraper");
return;
}
if(
!isset($_GET["target"]) ||
!is_string($_GET["target"])
){
$this->do400("Missing or invalid target");
return;
}
$scraper = $_GET["scraper"];
$target = $_GET["target"];
try{
include "resolve/{$scraper}.php";
$resolver = new $scraper();
$link = $resolver->resolve($target);
if(
is_string($link) &&
!empty($link)
){
header("Location: {$link}");
echo 'If your ass is not getting redirected, <a href="' . htmlspecialchars($link) . '">click here</a>.';
}
}catch(Exception $error){
$this->do404("Fuck! Failed to resolve URL: " . $error->getMessage());
}
}
public function do400($message){
header("Content-Type: text/plain");
http_response_code(400);
echo htmlspecialchars($message);
}
public function do404($message){
header("Content-Type: text/plain");
http_response_code(404);
echo htmlspecialchars($message);
}
}
+21 -26
View File
@@ -1,28 +1,23 @@
# When the robots.txt is sus # 44
# 4444444 44
# ⠀⠀⠀⡯⡯⡾⠝⠘⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢊⠘⡮⣣⠪⠢⡑⡌ # 44444444 44444 444
# ⠀⠀⠀⠟⠝⠈⠀⠀⠀⠡⠀⠠⢈⠠⢐⢠⢂⢔⣐⢄⡂⢔⠀⡁⢉⠸⢨⢑⠕⡌ # 44444444 444444 444444444
# ⠀⠀⡀⠁⠀⠀⠀⡀⢂⠡⠈⡔⣕⢮⣳⢯⣿⣻⣟⣯⣯⢷⣫⣆⡂⠀⠀⢐⠑⡌ # 44444 44444444 444444444
# ⢀⠠⠐⠈⠀⢀⢂⠢⡂⠕⡁⣝⢮⣳⢽⡽⣾⣻⣿⣯⡯⣟⣞⢾⢜⢆⠀⡀⠀⠪ # 444444444 4444444
# ⣬⠂⠀⠀⢀⢂⢪⠨⢂⠥⣺⡪⣗⢗⣽⢽⡯⣿⣽⣷⢿⡽⡾⡽⣝⢎⠀⠀⠀⢡ # 4444444444 444444
# ⣿⠀⠀⠀⢂⠢⢂⢥⢱⡹⣪⢞⡵⣻⡪⡯⡯⣟⡾⣿⣻⡽⣯⡻⣪⠧⠑⠀⠁⢐ # 4444444444444
# ⣿⠀⠀⠀⠢⢑⠠⠑⠕⡝⡎⡗⡝⡎⣞⢽⡹⣕⢯⢻⠹⡹⢚⠝⡷⡽⡨⠀⠀⢔ # 444444444444444444
# ⣿⡯⠀⢈⠈⢄⠂⠂⠐⠀⠌⠠⢑⠱⡱⡱⡑⢔⠁⠀⡀⠐⠐⠐⡡⡹⣪⠀⠀⢘ # 444444444444444
# ⣿⣽⠀⡀⡊⠀⠐⠨⠈⡁⠂⢈⠠⡱⡽⣷⡑⠁⠠⠑⠀⢉⢇⣤⢘⣪⢽⠀⢌⢎ # 44444444
# ⣿⢾⠀⢌⠌⠀⡁⠢⠂⠐⡀⠀⢀⢳⢽⣽⡺⣨⢄⣑⢉⢃⢭⡲⣕⡭⣹⠠⢐⢗ # 4444
# ⣿⡗⠀⠢⠡⡱⡸⣔⢵⢱⢸⠈⠀⡪⣳⣳⢹⢜⡵⣱⢱⡱⣳⡹⣵⣻⢔⢅⢬⡷ # 44
# ⣷⡇⡂⠡⡑⢕⢕⠕⡑⠡⢂⢊⢐⢕⡝⡮⡧⡳⣝⢴⡐⣁⠃⡫⡒⣕⢏⡮⣷⡟
# ⣷⣻⣅⠑⢌⠢⠁⢐⠠⠑⡐⠐⠌⡪⠮⡫⠪⡪⡪⣺⢸⠰⠡⠠⠐⢱⠨⡪⡪⡰
# ⣯⢷⣟⣇⡂⡂⡌⡀⠀⠁⡂⠅⠂⠀⡑⡄⢇⠇⢝⡨⡠⡁⢐⠠⢀⢪⡐⡜⡪⡊
# ⣿⢽⡾⢹⡄⠕⡅⢇⠂⠑⣴⡬⣬⣬⣆⢮⣦⣷⣵⣷⡗⢃⢮⠱⡸⢰⢱⢸⢨⢌
# ⣯⢯⣟⠸⣳⡅⠜⠔⡌⡐⠈⠻⠟⣿⢿⣿⣿⠿⡻⣃⠢⣱⡳⡱⡩⢢⠣⡃⠢⠁
# ⡯⣟⣞⡇⡿⣽⡪⡘⡰⠨⢐⢀⠢⢢⢄⢤⣰⠼⡾⢕⢕⡵⣝⠎⢌⢪⠪⡘⡌⠀
# ⡯⣳⠯⠚⢊⠡⡂⢂⠨⠊⠔⡑⠬⡸⣘⢬⢪⣪⡺⡼⣕⢯⢞⢕⢝⠎⢻⢼⣀⠀
# ⠁⡂⠔⡁⡢⠣⢀⠢⠀⠅⠱⡐⡱⡘⡔⡕⡕⣲⡹⣎⡮⡏⡑⢜⢼⡱⢩⣗⣯⣟
# ⢀⢂⢑⠀⡂⡃⠅⠊⢄⢑⠠⠑⢕⢕⢝⢮⢺⢕⢟⢮⢊⢢⢱⢄⠃⣇⣞⢞⣞⢾
# ⢀⠢⡑⡀⢂⢊⠠⠁⡂⡐⠀⠅⡈⠪⠪⠪⠣⠫⠑⡁⢔⠕⣜⣜⢦⡰⡎⡯⡾⡽
User-agent: * User-agent: *
Disallow: Disallow: /web
Host: 4get.ca Disallow: /images
Sitemap: https://4get.ca/sitemap Disallow: /videos
Disallow: /news
Disallow: /music
Disallow: /proxy
Disallow: /api/v1/ac
Allow: /
+2243
View File
File diff suppressed because it is too large. Load diff
+436 -332
View File
@@ -210,6 +210,47 @@ class brave{
return $data; return $data;
} }
private function get_js(){
$script_bodies =
$this->fuckhtml
->getElementsByTagName(
"script"
);
$datas = [];
foreach($script_bodies as &$script){
$jsons = explode("{type:\"data\"", $script["innerHTML"]);
array_shift($jsons);
foreach($jsons as $json){
$data =
$this->fuckhtml
->parseJsObject(
$this->fuckhtml
->extract_json(
"{type:\"data\"" .
$json
)
);
if($data !== []){
$datas[] = $data;
}
}
}
if(count($datas) === 0){
throw new Exception("Could not grep any JavaScript object");
}
return $datas;
}
public function web($get){ public function web($get){
if($get["npt"]){ if($get["npt"]){
@@ -290,12 +331,8 @@ class brave{
$q["spellcheck"] = "0"; $q["spellcheck"] = "0";
} }
} }
/*
$handle = fopen("scraper/brave.html", "r");
$html = fread($handle, filesize("scraper/brave.html"));
fclose($handle);
*/
//$html = file_get_contents("scraper/brave.html");
try{ try{
$html = $html =
$this->get( $this->get(
@@ -330,23 +367,25 @@ class brave{
// load html // load html
$this->fuckhtml->load($html); $this->fuckhtml->load($html);
$this->detect_captcha($html);
/* /*
Get next page "token" Get next page "token"
*/ */
$nextpage = $nextpage =
$this->fuckhtml $this->fuckhtml
->getElementById( ->getElementsByClassName(
"pagination", "pagination",
"div" "div"
); );
if($nextpage){ if(count($nextpage) !== 0){
$this->fuckhtml->load($nextpage); $this->fuckhtml->load($nextpage[0]);
$nextpage = $nextpage =
$this->fuckhtml $this->fuckhtml
->getElementsByClassName("btn", "a"); ->getElementsByClassName("button", "a");
if(count($nextpage) !== 0){ if(count($nextpage) !== 0){
@@ -382,46 +421,13 @@ class brave{
} }
} }
// do some magic
$this->fuckhtml->load($html); $this->fuckhtml->load($html);
$datas = $this->get_js();
$script_disc = $has_found_body = false;
$this->fuckhtml
->getElementsByTagName(
"script"
);
$grep = [];
foreach($script_disc as $discs){
preg_match(
'/const data ?= ?(\[{.*}]);/',
$discs["innerHTML"],
$grep
);
if(isset($grep[1])){
break;
}
}
if(!isset($grep[1])){
throw new Exception("Could not grep JavaScript object");
}
$data =
$this->fuckhtml
->parseJsObject(
$grep[1]
);
unset($grep);
if($data === null){
throw new Exception("Failed to decode JavaScript object");
}
foreach($datas as $data){
if( if(
isset($data[2]["data"]["title"]) && isset($data[2]["data"]["title"]) &&
stripos($data[2]["data"]["title"], "PoW Captcha") !== false stripos($data[2]["data"]["title"], "PoW Captcha") !== false
@@ -430,12 +436,13 @@ class brave{
throw new Exception("Brave returned a PoW captcha"); throw new Exception("Brave returned a PoW captcha");
} }
if(!isset($data[1]["data"]["body"]["response"])){ if(!isset($data["data"]["body"]["response"])){
throw new Exception("Brave did not return a result object"); continue;
} }
$data = $data[1]["data"]["body"]["response"]; $has_found_body = true;
$data = $data["data"]["body"]["response"];
/* /*
Get web results Get web results
@@ -453,7 +460,7 @@ class brave{
){ ){
$thumb = [ $thumb = [
"ratio" => $result["thumbnail"]["logo"] == "false" ? "16:9" : "1:1", "ratio" => (isset($result["thumbnail"]["logo"]) && $result["thumbnail"]["logo"] == "true") ? "1:1" : "16:9",
"url" => $result["thumbnail"]["original"] "url" => $result["thumbnail"]["original"]
]; ];
}else{ }else{
@@ -663,7 +670,10 @@ class brave{
$table["Address"] = $result["location"]["postal_address"]["displayAddress"]; $table["Address"] = $result["location"]["postal_address"]["displayAddress"];
} }
if(isset($result["location"]["rating"])){ if(
isset($result["location"]["rating"]) &&
$result["location"]["rating"] != "void 0"
){
$table["Rating"] = $table["Rating"] =
$result["location"]["rating"]["ratingValue"] . "/" . $result["location"]["rating"]["ratingValue"] . "/" .
@@ -671,13 +681,19 @@ class brave{
number_format($result["location"]["rating"]["reviewCount"]) . " votes)"; number_format($result["location"]["rating"]["reviewCount"]) . " votes)";
} }
if(isset($result["location"]["contact"]["telephone"])){ if(
isset($result["location"]["contact"]["telephone"]) &&
$result["location"]["contact"]["telephone"] != "void 0"
){
$table["Phone number"] = $table["Phone number"] =
$result["location"]["contact"]["telephone"]; $result["location"]["contact"]["telephone"];
} }
if(isset($result["location"]["price_range"])){ if(
isset($result["location"]["price_range"]) &&
$result["location"]["price_range"] != "void 0"
){
$table["Price"] = $table["Price"] =
$result["location"]["price_range"]; $result["location"]["price_range"];
@@ -946,11 +962,23 @@ class brave{
} }
} }
$thumb = isset($info["images"][0]["original"]) ? $info["images"][0]["original"] : null;
if(
preg_match(
'/^\/\//',
$thumb
) !== 0
){
$thumb = "https:" . $thumb;
}
$out["answer"][] = [ $out["answer"][] = [
"title" => $this->fuckhtml->getTextContent($info["title"]), "title" => $this->fuckhtml->getTextContent($info["title"]),
"description" => $description, "description" => $description,
"url" => $info["url"], "url" => $info["url"],
"thumb" => isset($info["images"][0]["original"]) ? $info["images"][0]["original"] : null, "thumb" => $thumb,
"table" => $table, "table" => $table,
"sublink" => $sublink "sublink" => $sublink
]; ];
@@ -966,6 +994,25 @@ class brave{
foreach($data["videos"]["results"] as $video){ foreach($data["videos"]["results"] as $video){
if(
$video["title"] == "" &&
$video["description"] == ""
){
// ignore result
continue;
}
// get thumbnails
$thumbs = [];
if(isset($video["video"]["thumbnail"]["src"])){ $thumbs[] = $video["video"]["thumbnail"]["src"]; }
if(isset($video["video"]["thumbnail"]["original"])){ $thumbs[] = $video["video"]["thumbnail"]["original"]; }
if(isset($video["thumbnail"]["src"])){ $thumbs[] = $video["thumbnail"]["src"]; }
if(isset($video["thumbnail"]["original"])){ $thumbs[] = $video["thumbnail"]["original"]; }
$thumb = $this->check_thumbs($thumbs);
$out["video"][] = [ $out["video"][] = [
"title" => $this->titledots($video["title"]), "title" => $this->titledots($video["title"]),
"description" => $this->titledots($video["description"]), "description" => $this->titledots($video["description"]),
@@ -973,10 +1020,10 @@ class brave{
"duration" => isset($video["video"]["duration"]) && $video["video"]["duration"] != "void 0" ? $this->hms2int($video["video"]["duration"]) : null, "duration" => isset($video["video"]["duration"]) && $video["video"]["duration"] != "void 0" ? $this->hms2int($video["video"]["duration"]) : null,
"views" => isset($video["video"]["views"]) && $video["video"]["views"] != "void 0" ? (int)$video["video"]["views"] : null, "views" => isset($video["video"]["views"]) && $video["video"]["views"] != "void 0" ? (int)$video["video"]["views"] : null,
"thumb" => "thumb" =>
isset($video["thumbnail"]["src"]) ? $thumb !== false ?
[ [
"ratio" => "16:9", "ratio" => "16:9",
"url" => $this->unshiturl($video["thumbnail"]["src"]) "url" => $this->unshiturl($thumb)
] : ] :
[ [
"ratio" => null, "ratio" => null,
@@ -1063,6 +1110,267 @@ class brave{
// append discussions at position 2 // append discussions at position 2
array_splice($out["web"], 1, 0, $disc_out); array_splice($out["web"], 1, 0, $disc_out);
}
if($has_found_body === false){
throw new Exception("Brave did not return a search result object");
}
return $out;
}
public function image($get){
$search = $get["s"];
if(strlen($search) === 0){
throw new Exception("Search term is empty!");
}
if(strlen($search) > 2048){
throw new Exception("Search term is too long!");
}
$country = $get["country"];
$nsfw = $get["nsfw"];
$spellcheck = $get["spellcheck"] == "yes" ? "1" : "0";
$out = [
"status" => "ok",
"npt" => null,
"image" => []
];
try{
$html =
$this->get(
$this->backend->get_ip(), // no nextpage right now, pass proxy directly
"https://search.brave.com/images",
[
"q" => $search,
"spellcheck" => $spellcheck
],
$nsfw,
$country
);
}catch(Exception $error){
throw new Exception("Could not fetch search page");
}
/*
$handle = fopen("scraper/brave-image.html", "r");
$html = fread($handle, filesize("scraper/brave-image.html"));
fclose($handle);*/
$this->fuckhtml->load($html);
$this->detect_captcha($html);
$jsons = $this->get_js();
foreach($jsons as $json){
if(!isset($json["data"]["body"]["response"]["results"])){
continue;
}
foreach(
$json
["data"]
["body"]
["response"]
["results"]
as $result
){
$out["image"][] = [
"title" => $result["title"],
"source" => [
[
"url" => $result["properties"]["url"],
"width" => (int)$result["properties"]["width"],
"height" => (int)$result["properties"]["height"]
],
[
"url" => $result["thumbnail"]["src"],
"width" => (int)$result["thumbnail"]["width"],
"height" => (int)$result["thumbnail"]["height"]
]
],
"url" => $result["url"]
];
}
}
return $out;
}
public function video($get){
if($get["npt"]){
[$npt, $proxy] = $this->backend->get($get["npt"], "videos");
$npt = json_decode($npt, true);
$search = $npt["q"];
$offset = $npt["offset"];
$spellcheck = $npt["spellcheck"];
$country = $npt["country"];
$nsfw = $npt["nsfw"];
try{
$html =
$this->get(
$proxy,
"https://search.brave.com/videos",
[
"q" => $search,
"offset" => $offset,
"spellcheck" => $spellcheck
],
$nsfw,
$country
);
}catch(Exception $error){
throw new Exception("Could not fetch search page");
}
}else{
$search = $get["s"];
if(strlen($search) === 0){
throw new Exception("Search term is empty!");
}
if(strlen($search) > 2048){
throw new Exception("Search term is too long!");
}
$country = $get["country"];
$nsfw = $get["nsfw"];
$spellcheck = $get["spellcheck"] == "yes" ? "1" : "0";
$proxy = $this->backend->get_ip();
try{
$html =
$this->get(
$proxy,
"https://search.brave.com/videos",
[
"q" => $search,
"spellcheck" => $spellcheck
],
$nsfw,
$country
);
}catch(Exception $error){
throw new Exception("Could not fetch search page");
}
}
$this->fuckhtml->load($html);
$this->detect_captcha($html);
$out = [
"status" => "ok",
"npt" => null,
"video" => [],
"author" => [],
"livestream" => [],
"playlist" => [],
"reel" => []
];
// get npt
$out["npt"] =
$this->generatenextpagetoken(
$search,
$nsfw,
$country,
$spellcheck,
"videos",
$proxy
);
/*
$handle = fopen("scraper/brave-video.html", "r");
$html = fread($handle, filesize("scraper/brave-video.html"));
fclose($handle);*/
$this->fuckhtml->load($html);
$jsons = $this->get_js();
$error = false;
foreach($jsons as $json){
if(!isset($json["data"]["body"]["response"]["results"])){
continue;
}
foreach(
$json
["data"]
["body"]
["response"]
["results"]
as $result
){
if($result["video"]["author"] != "null"){
$author = [
"name" => $result["video"]["author"]["name"] == "null" ? null : $result["video"]["author"]["name"],
"url" => $result["video"]["author"]["url"] == "null" ? null : $result["video"]["author"]["url"],
"avatar" => $result["video"]["author"]["img"] == "null" ? null : $result["video"]["author"]["img"]
];
}else{
$author = [
"name" => null,
"url" => null,
"avatar" => null
];
}
if($result["thumbnail"] != "null"){
$thumb = [
"url" => $result["thumbnail"]["original"],
"ratio" => "16:9"
];
}else{
$thumb = [
"url" => null,
"ratio" => null
];
}
$out["video"][] = [
"title" => $result["title"],
"description" => $result["description"] == "null" ? null : $this->titledots($result["description"]),
"author" => $author,
"date" => ($result["age"] == "null" || $result["age"] == "void 0") ? null : strtotime($result["age"]),
"duration" => $result["video"]["duration"] == "null" ? null : $this->hms2int($result["video"]["duration"]),
"views" => $result["video"]["views"] == "null" ? null : (int)$result["video"]["views"],
"thumb" => $thumb,
"url" => $result["url"]
];
}
}
return $out; return $out;
} }
@@ -1149,6 +1457,8 @@ class brave{
// load html // load html
$this->fuckhtml->load($html); $this->fuckhtml->load($html);
$this->detect_captcha($html);
// get npt // get npt
$out["npt"] = $out["npt"] =
$this->generatenextpagetoken( $this->generatenextpagetoken(
@@ -1160,26 +1470,18 @@ class brave{
$proxy $proxy
); );
preg_match( $this->fuckhtml->load($html);
'/const data ?= ?(\[{.*}]);/', $jsons = $this->get_js();
$html,
$json
);
if(!isset($json[1])){ foreach($jsons as $json){
throw new Exception("Failed to grep javascript object"); if(!isset($json["data"]["response"]["news"]["results"])){
}
$json = $this->fuckhtml->parseJsObject($json[1], true); continue;
if($json === null){
throw new Exception("Failed to parse javascript object");
} }
foreach( foreach(
$json[1]["data"]["body"]["response"]["news"]["results"] $json["data"]["response"]["news"]["results"]
as $news as $news
){ ){
@@ -1209,267 +1511,6 @@ class brave{
"url" => $news["url"] "url" => $news["url"]
]; ];
} }
return $out;
}
public function image($get){
$search = $get["s"];
if(strlen($search) === 0){
throw new Exception("Search term is empty!");
}
if(strlen($search) > 2048){
throw new Exception("Search term is too long!");
}
$country = $get["country"];
$nsfw = $get["nsfw"];
$spellcheck = $get["spellcheck"] == "yes" ? "1" : "0";
$out = [
"status" => "ok",
"npt" => null,
"image" => []
];
try{
$html =
$this->get(
$this->backend->get_ip(), // no nextpage right now, pass proxy directly
"https://search.brave.com/images",
[
"q" => $search,
"spellcheck" => $spellcheck
],
$nsfw,
$country
);
}catch(Exception $error){
throw new Exception("Could not fetch search page");
}
/*
$handle = fopen("scraper/brave-image.html", "r");
$html = fread($handle, filesize("scraper/brave-image.html"));
fclose($handle);*/
preg_match(
'/const data = (\[{.*}\]);/',
$html,
$json
);
if(!isset($json[1])){
throw new Exception("Failed to get data object");
}
$json =
$this->fuckhtml
->parseJsObject(
$json[1]
);
foreach(
$json[1]
["data"]
["body"]
["response"]
["results"]
as $result
){
$out["image"][] = [
"title" => $result["title"],
"source" => [
[
"url" => $result["properties"]["url"],
"width" => null,
"height" => null
],
[
"url" => $result["thumbnail"]["src"],
"width" => null,
"height" => null
]
],
"url" => $result["url"]
];
}
return $out;
}
public function video($get){
if($get["npt"]){
[$npt, $proxy] = $this->backend->get($get["npt"], "videos");
$npt = json_decode($npt, true);
$search = $npt["q"];
$offset = $npt["offset"];
$spellcheck = $npt["spellcheck"];
$country = $npt["country"];
$nsfw = $npt["nsfw"];
try{
$html =
$this->get(
$proxy,
"https://search.brave.com/videos",
[
"q" => $search,
"offset" => $offset,
"spellcheck" => $spellcheck
],
$nsfw,
$country
);
}catch(Exception $error){
throw new Exception("Could not fetch search page");
}
}else{
$search = $get["s"];
if(strlen($search) === 0){
throw new Exception("Search term is empty!");
}
if(strlen($search) > 2048){
throw new Exception("Search term is too long!");
}
$country = $get["country"];
$nsfw = $get["nsfw"];
$spellcheck = $get["spellcheck"] == "yes" ? "1" : "0";
$proxy = $this->backend->get_ip();
try{
$html =
$this->get(
$proxy,
"https://search.brave.com/videos",
[
"q" => $search,
"spellcheck" => $spellcheck
],
$nsfw,
$country
);
}catch(Exception $error){
throw new Exception("Could not fetch search page");
}
}
$this->fuckhtml->load($html);
$out = [
"status" => "ok",
"npt" => null,
"video" => [],
"author" => [],
"livestream" => [],
"playlist" => [],
"reel" => []
];
// get npt
$out["npt"] =
$this->generatenextpagetoken(
$search,
$nsfw,
$country,
$spellcheck,
"videos",
$proxy
);
/*
$handle = fopen("scraper/brave-video.html", "r");
$html = fread($handle, filesize("scraper/brave-video.html"));
fclose($handle);*/
preg_match(
'/const data = (\[{.*}\]);/',
$html,
$json
);
if(!isset($json[1])){
throw new Exception("Failed to get data object");
}
$json =
$this->fuckhtml
->parseJsObject(
$json[1]
);
foreach(
$json
[1]
["data"]
["body"]
["response"]
["results"]
as $result
){
if($result["video"]["author"] != "null"){
$author = [
"name" => $result["video"]["author"]["name"] == "null" ? null : $result["video"]["author"]["name"],
"url" => $result["video"]["author"]["url"] == "null" ? null : $result["video"]["author"]["url"],
"avatar" => $result["video"]["author"]["img"] == "null" ? null : $result["video"]["author"]["img"]
];
}else{
$author = [
"name" => null,
"url" => null,
"avatar" => null
];
}
if($result["thumbnail"] != "null"){
$thumb = [
"url" => $result["thumbnail"]["original"],
"ratio" => "16:9"
];
}else{
$thumb = [
"url" => null,
"ratio" => null
];
}
$out["video"][] = [
"title" => $result["title"],
"description" => $result["description"] == "null" ? null : $this->titledots($result["description"]),
"author" => $author,
"date" => ($result["age"] == "null" || $result["age"] == "void 0") ? null : strtotime($result["age"]),
"duration" => $result["video"]["duration"] == "null" ? null : $this->hms2int($result["video"]["duration"]),
"views" => $result["video"]["views"] == "null" ? null : (int)$result["video"]["views"],
"thumb" => $thumb,
"url" => $result["url"]
];
} }
return $out; return $out;
@@ -1790,7 +1831,21 @@ class brave{
$nextpage = $nextpage =
$this->fuckhtml $this->fuckhtml
->getElementsByClassName("btn", "a"); ->getElementsByClassName(
"pagination",
"div"
);
if(count($nextpage) !== 0){
$this->fuckhtml->load($nextpage[0]);
$nextpage =
$this->fuckhtml
->getElementsByClassName(
"button",
"a"
);
if(count($nextpage) !== 0){ if(count($nextpage) !== 0){
@@ -1828,6 +1883,7 @@ class brave{
); );
} }
} }
}
return null; return null;
} }
@@ -1855,4 +1911,52 @@ class brave{
) )
); );
} }
private function check_thumbs($thumbs){
foreach($thumbs as $thumb){
if(
preg_match(
'/^http/',
$thumb
) === 1
){
return $this->unshiturl($thumb);
}
}
return false;
}
private function detect_captcha(){
$title =
$this->fuckhtml
->getElementsByTagName(
"title"
);
if(count($title) === 0){
if(strtolower($this->fuckhtml->getloadedhtml()) == "this service is not available in your region"){
throw new Exception("Brave rangebanned the IP range");
}
throw new Exception("Brave returned a malformed page");
}
$title =
strtolower(
$this->fuckhtml
->getTextContent($title[0])
);
if($title == "human verification"){
throw new Exception("Brave returned a Captcha");
}
}
} }
+923
View File
@@ -0,0 +1,923 @@
<?php
class coccoc{
public function __construct(){
include "lib/backend.php";
$this->backend = new backend("coccoc");
include "lib/fuckhtml.php";
$this->fuckhtml = new fuckhtml();
$this->proc = curl_multi_init();
$this->handles = [];
}
private function get($proxy, $url, $cookie = false, $get = []){
$curlproc = curl_init();
if($get !== []){
$get = http_build_query($get);
$url .= "?" . $get;
}
curl_setopt($curlproc, CURLOPT_URL, $url);
// http2 bypass
curl_setopt($curlproc, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
if($cookie === false){
curl_setopt($curlproc, CURLOPT_HTTPHEADER, [
"User-Agent: " . config::USER_AGENT,
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language: en-CA,en-US;q=0.9,en;q=0.8",
"Accept-Encoding: gzip, deflate, br, zstd",
"DNT: 1",
"Sec-GPC: 1",
"Connection: keep-alive",
"Upgrade-Insecure-Requests: 1",
"Sec-Fetch-Dest: document",
"Sec-Fetch-Mode: navigate",
"Sec-Fetch-Site: none",
"Sec-Fetch-User: ?1",
"Priority: u=0, i"
]);
}else{
curl_setopt($curlproc, CURLOPT_HTTPHEADER, [
"User-Agent: " . config::USER_AGENT,
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language: en-CA,en-US;q=0.9,en;q=0.8",
"Accept-Encoding: gzip, deflate, br, zstd",
"DNT: 1",
"Sec-GPC: 1",
"Connection: keep-alive",
"Referer: https://coccoc.com/search",
"Cookie: {$cookie}",
"Upgrade-Insecure-Requests: 1",
"Sec-Fetch-Dest: document",
"Sec-Fetch-Mode: navigate",
"Sec-Fetch-Site: same-origin",
"Priority: u=0, i",
"TE: trailers"
]);
}
$this->backend->assign_proxy($curlproc, $proxy);
curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
$data = curl_exec($curlproc);
if(curl_errno($curlproc)){
throw new Exception(curl_error($curlproc));
}
curl_close($curlproc);
return $data;
}
public function getfilters($pagetype){
return [
"nsfw" => [
"display" => "NSFW",
"option" => [
"yes" => "Yes", // nsfw by default????
"no" => "No" // &safe=1
]
],
"time" => [
"display" => "Time posted",
"option" => [
"any" => "Any time",
"1w" => "1 week ago",
"2w" => "2 weeks ago",
"1m" => "1 month ago",
"3m" => "3 months ago",
"6m" => "6 months ago",
"1Y" => "1 year ago"
]
],
"filter" => [
"display" => "Remove duplicates",
"option" => [
"no" => "No",
"yes" => "Yes" // &filter=0
]
]
];
}
private function handle_redir($proxy, $html){
$this->fuckhtml->load($html);
$title =
$this->fuckhtml
->getElementsByTagName("title");
if(
count($title) !== 0 &&
$this->fuckhtml
->getTextContent(
$title[0]
) == "Redirecting..."
){
// get cookie
if(
preg_match(
'/document\.cookie ?= ?"([^;"]+).*"/',
$html,
$cookie
) === 0
){
throw new Exception("Failed to grep cookie");
}
// get redirect URL
if(
preg_match(
'/window\.location\.replace\("([^"]+)"\)/',
$html,
$redir
) === 0
){
throw new Exception("Failed to grep redirection URL");
}
try{
$html =
$this->get(
$proxy,
$redir[1],
$cookie[1],
[]
);
}catch(Exception $error){
throw new Exception("Failed to get redirection page");
}
}
return $html;
}
private function redirect_add_url($url){
// https://coccoc.com/composer/goto?url=0AWtXcuK-mJVvFl8Qhny9LZdMOyEd08nFmTtpg82bmuqtp8p8G_OnTebNhPtNyIXMGQ1TZZ-ADMz0QEiFJhsTLIFkkPDXnJjHo3kqsobmyntiOhFag-2aqTs
if(
preg_match(
'/\/goto\?url=([^&]+)/',
$url,
$slug
) === 0
){
// not a coccoc redirect
return $url;
}
$curlproc = curl_init();
curl_setopt($curlproc, CURLOPT_URL, "https://coccoc.com/composer/goto?url=" . urlencode($slug[1]));
curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
curl_setopt($curlproc, CURLOPT_HTTPHEADER,
["User-Agent: " . config::USER_AGENT,
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip, deflate, br, zstd",
"DNT: 1",
"Sec-GPC: 1",
"Connection: keep-alive",
"Upgrade-Insecure-Requests: 1",
"Sec-Fetch-Dest: document",
"Sec-Fetch-Mode: navigate",
"Sec-Fetch-Site: none",
"Sec-Fetch-User: ?1",
"Priority: u=0, i"]
);
curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
// funny
curl_setopt($curlproc, CURLOPT_HEADER, true);
//$this->backend->assign_proxy($curlproc, $proxy);
curl_multi_add_handle($this->proc, $curlproc);
$this->handles[] = [
"replacement" => $url,
"handle" => $curlproc
];
return $url;
}
private function resolve_urls(&$scraped_data){
do{
do{
$status = curl_multi_exec($this->proc, $active);
}while($status === CURLM_CALL_MULTI_PERFORM);
if($active){
curl_multi_select($this->proc);
}
}while($active && $status === CURLM_OK);
//
// if we reach this, we're done downloading garbage
//
// parse redirect values & create data shit
$keys = [];
$replacements = [];
for($i=0; $i<count($this->handles); $i++){
$headers =
curl_multi_getcontent(
$this->handles[$i]["handle"]
);
if(
!preg_match(
'/^location: ?([^ ]+)/im',
$headers,
$link
)
){
// failed to extract redirect location
continue;
}
// theyre too retarded to encode shit properly apparently.
// this is the best i can do, it will still yield errors when searching for "higurashi"
// example: https://coccoc.com/composer/goto?url=0AWtXcuIKWPOkxvarGvy3GJ36XipRirzzqAvltMM5QUfB1RPu49LrNB9qmuAQyvvI0eCTwf-FpjRjxUN8XbruhA0rvCHNNlhVNEozMUqgRcjzsdhyUMCEgwLCXVTQkXnLXqrUw8celcIZdfmZ1geOvioUcg
// redirects to https://vi.wikipedia.org/wiki/Higurashi_no_Naku_Koro_ni#C%E1ch_ch?i
// notice the encoding errors after #
// retards
$link = mb_convert_encoding($link[1], "UTF-8", "ISO-8859-1");
if(
preg_match(
'/^(https?:|)\/\//',
$link
)
){
$keys[] = $this->handles[$i]["replacement"];
$replacements[] = $link;
}
}
$this->recursive_replace($keys, $replacements, $scraped_data);
curl_multi_close($this->proc);
}
private function recursive_replace(&$keys, &$replacements, &$pointer){
foreach($pointer as $key => &$value){
if($key == "url"){
$search =
array_search(
$value,
$keys
);
if($search !== false){
// replace url
$value = $replacements[$search];
}
}elseif(is_array($value)){
$this->recursive_replace($keys, $replacements, $value);
}
}
}
public function web($get){
if($get["npt"]){
[$query, $proxy] =
$this->backend->get(
$get["npt"],
"web"
);
$query = json_decode($query, true);
}else{
$proxy = $this->backend->get_ip();
$query = [
"query" => $get["s"]
];
// add filters
if($get["nsfw"] == "no"){
$query["safe"] = 1;
}
if($get["time"] != "any"){
$query["tbs"] = $get["time"];
}
if($get["filter"] == "yes"){
$query["filter"] = 0;
}
}
try{
$html =
$this->get(
$proxy,
"https://coccoc.com/search",
false,
$query
);
}catch(Exception $error){
throw new Exception("Failed to get search page");
}
//$html = file_get_contents("scraper/coccoc.html");
$html = $this->handle_redir($proxy, $html);
$html = explode("window.composerResponse", $html, 2);
if(count($html) !== 2){
throw new Exception("Failed to grep window.composerResponse");
}
$html =
json_decode(
$this->fuckhtml
->extract_json(
ltrim($html[1], " =")
),
true
);
if($html === null){
throw new Exception("Failed to decode JSON");
}
if(
isset($html["captcha"]) &&
(int)$html["captcha"] === 1
){
throw new Exception("Coc Coc returned a Captcha");
}
if(!isset($html["search"]["search_results"])){
throw new Exception("Coc Coc did not return a search_results object");
}
$out = [
"status" => "ok",
"spelling" => [
"type" => "no_correction",
"using" => null,
"correction" => null
],
"npt" => null,
"answer" => [],
"web" => [],
"image" => [],
"video" => [],
"news" => [],
"related" => []
];
// word correction
foreach($html["top"] as $element){
if(isset($element["spellChecker"][0]["query"])){
$out["spelling"] = [
"type" => "not_many",
"using" => $html["search"]["query"],
"correction" => $element["spellChecker"][0]["query"]
];
}
}
foreach($html["search"]["search_results"] as $result){
if(isset($result["type"])){
switch($result["type"]){
//
// Related searches
//
case "related_queries":
$out["related"] = $result["queries"];
continue 2;
//
// Videos
//
case "video_hits":
foreach($result["results"] as $video){
if(
isset($video["image_url"]) &&
!empty($video["image_url"])
){
$thumb = [
"ratio" => "16:9",
"url" => $video["image_url"]
];
}else{
$thumb = [
"ratio" => null,
"url" => null
];
}
$out["video"][] = [
"title" =>
$this->titledots(
$this->fuckhtml
->getTextContent(
$video["title"]
)
),
"description" => null,
"author" => [
"name" => $video["uploader"],
"url" => null,
"avatar" => null
],
"date" => (int)$video["date"],
"duration" => (int)$video["duration"],
"views" => null,
"thumb" => $thumb,
"url" => $video["url"]
];
}
continue 2;
}
}
if(
!isset($result["title"]) ||
!isset($result["url"])
){
// should not happen
continue;
}
if(isset($result["rich"]["data"]["image_url"])){
$thumb = [
"url" => $result["rich"]["data"]["image_url"],
"ratio" => "16:9"
];
}else{
$thumb = [
"url" => null,
"ratio" => null
];
}
$sublinks = [];
if(isset($result["rich"]["data"]["linked_docs"])){
foreach($result["rich"]["data"]["linked_docs"] as $sub){
$sublinks[] = [
"title" =>
$this->titledots(
$this->fuckhtml
->getTextContent(
$sub["title"]
)
),
"description" =>
$this->titledots(
$this->fuckhtml
->getTextContent(
$sub["content"]
)
),
"date" => null,
"url" => $this->redirect_add_url($sub["url"])
];
}
}
// get date
if(isset($result["date"])){
$date = (int)$result["date"];
}else{
$date = null;
}
// probe for metadata
$table = [];
if(isset($result["rich"]["data"]["rating"])){
$table["Rating"] = $result["rich"]["data"]["rating"];
if(isset($result["rich"]["data"]["num_rating"])){
$table["Rating"] .= " (" . number_format($result["rich"]["data"]["num_rating"]) . " ratings)";
}
}
if(isset($result["rich"]["data"]["views"])){
$table["Views"] = number_format($result["rich"]["data"]["views"]);
}
if(isset($result["rich"]["data"]["duration"])){
$table["Duration"] = $this->int2hms($result["rich"]["data"]["duration"]);
}
if(isset($result["rich"]["data"]["channel_name"])){
$table["Author"] = $result["rich"]["data"]["channel_name"];
}
if(isset($result["rich"]["data"]["video_quality"])){
$table["Quality"] = $result["rich"]["data"]["video_quality"];
}
if(isset($result["rich"]["data"]["category"])){
$table["Category"] = $result["rich"]["data"]["category"];
}
$out["web"][] = [
"title" =>
$this->titledots(
$this->fuckhtml
->getTextContent(
$result["title"]
)
),
"description" =>
$this->titledots(
$this->fuckhtml
->getTextContent(
$result["content"]
)
),
"url" => $this->redirect_add_url($result["url"]),
"date" => $date,
"type" => "web",
"thumb" => $thumb,
"sublink" => $sublinks,
"table" => $table
];
}
//
// Get wikipedia head
//
if(isset($html["right"])){
foreach($html["right"] as $wiki){
$description = [];
if(isset($wiki["short_intro"])){
$description[] =
[
"type" => "quote",
"value" => $wiki["short_intro"],
];
}
if(isset($wiki["intro"])){
$description[] =
[
"type" => "text",
"value" => $wiki["intro"],
];
}
// get table elements
$table = [];
if(isset($wiki["fields"])){
foreach($wiki["fields"] as $element){
$table[$element["title"]] = implode(", ", $element["value"]);
}
}
// get sublinks
$sublinks = [];
if(isset($wiki["website"])){
if(
preg_match(
'/^http/',
$wiki["website"]
) === 0
){
$sublinks["Website"] = "https://" . $wiki["website"];
}else{
$sublinks["Website"] = $wiki["website"];
}
}
if(isset($wiki["profiles"])){
foreach($wiki["profiles"] as $sitename => $url){
$sitename = explode("_", $sitename);
$sitename = ucfirst($sitename[count($sitename) - 1]);
$sublinks[$sitename] = $url;
}
}
$out["answer"][] = [
"title" =>
isset($wiki["title"]) ?
$this->titledots(
$wiki["title"]
) : null,
"description" => $description,
"url" => null,
"thumb" => isset($wiki["image"]["contentUrl"]) ? $wiki["image"]["contentUrl"] : null,
"table" => $table,
"sublink" => $sublinks
];
}
}
// get next page
if((int)$html["search"]["page"] < (int)$html["search"]["max_page"]){
// https://coccoc.com/composer?_=1754021153532&p=0&q=zbabduiqwhduwqhdnwq&reqid=bwcAs00q&s=direct&apiV=1
// ^json endpoint, but we can just do &page=2 lol
if(!isset($query["page"])){
$query["page"] = 2;
}else{
$query["page"]++;
}
$out["npt"] =
$this->backend
->store(
json_encode($query),
"web",
$proxy
);
}
$this->resolve_urls($out);
return $out;
}
public function video($get){
//$html = file_get_contents("scraper/coccoc.html");
if($get["npt"]){
[$query, $proxy] =
$this->backend->get(
$get["npt"],
"videos"
);
$query = json_decode($query, true);
}else{
$proxy = $this->backend->get_ip();
$query = [
"query" => $get["s"],
"tbm" => "vid"
];
// add filters
if($get["nsfw"] == "no"){
$query["safe"] = 1;
}
if($get["time"] != "any"){
$query["tbs"] = $get["time"];
}
if($get["filter"] == "yes"){
$query["filter"] = 0;
}
}
try{
$html =
$this->get(
$proxy,
"https://coccoc.com/search",
false,
$query
);
}catch(Exception $error){
throw new Exception("Failed to get search page");
}
$html = $this->handle_redir($proxy, $html);
$html = explode("window.composerResponse", $html, 2);
if(count($html) !== 2){
throw new Exception("Failed to grep window.composerResponse");
}
$html =
json_decode(
$this->fuckhtml
->extract_json(
ltrim($html[1], " =")
),
true
);
if($html === null){
throw new Exception("Failed to decode JSON");
}
$out = [
"status" => "ok",
"npt" => null,
"video" => [],
"author" => [],
"livestream" => [],
"playlist" => [],
"reel" => []
];
if(!isset($html["search_video"]["search_results"])){
if(isset($html["search_video"]["error"]["title"])){
if($html["search_video"]["error"]["title"] == "Không tìm thấy kết quả nào"){
return $out;
}
throw new Exception("Coc Coc returned an error: " . $html["search_video"]["error"]["title"]);
}
throw new Exception("Coc Coc did not supply a search_results object");
}
foreach($html["search_video"]["search_results"] as $video){
if(isset($video["rich"]["data"]["image_url"])){
$thumb = [
"ratio" => "16:9",
"url" => $video["rich"]["data"]["image_url"]
];
}else{
$thumb = [
"ratio" => null,
"url" => null
];
}
$out["video"][] = [
"title" =>
$this->titledots(
$this->fuckhtml
->getTextContent(
$video["title"]
)
),
"description" =>
$this->titledots(
$this->fuckhtml
->getTextContent(
$video["content"]
)
),
"author" => [
"name" =>
isset($video["rich"]["data"]["channel_name"]) ?
$video["rich"]["data"]["channel_name"] : null,
"url" => null,
"avatar" => null
],
"date" =>
isset($video["date"]) ?
$video["date"] : null,
"duration" =>
isset($video["rich"]["data"]["duration"]) ?
(int)$video["rich"]["data"]["duration"] : null,
"views" => null,
"thumb" => $thumb,
"url" => $this->redirect_add_url($video["url"])
];
}
// get next page
if((int)$html["search_video"]["page"] < (int)$html["search_video"]["max_page"]){
if(!isset($query["page"])){
$query["page"] = 2;
}else{
$query["page"]++;
}
$out["npt"] =
$this->backend
->store(
json_encode($query),
"videos",
$proxy
);
}
$this->resolve_urls($out);
return $out;
}
private function titledots($title){
return trim($title, " .\t\n\r\0\x0B…");
}
private function int2hms($seconds){
$hours = floor($seconds / 3600);
$minutes = floor(($seconds % 3600) / 60);
$seconds = $seconds % 60;
return sprintf("%02d:%02d:%02d", $hours, $minutes, $seconds);
}
}
-145
View File
@@ -1,145 +0,0 @@
<?php
class crowdview{
public function __construct(){
include "lib/backend.php";
$this->backend = new backend("crowdview");
include "lib/fuckhtml.php";
$this->fuckhtml = new fuckhtml();
}
public function getfilters($page){
return [];
}
private function get($proxy, $url, $get = []){
$curlproc = curl_init();
if($get !== []){
$get = http_build_query($get);
$url .= "?" . $get;
}
curl_setopt($curlproc, CURLOPT_URL, $url);
curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
curl_setopt($curlproc, CURLOPT_HTTPHEADER,
["User-Agent: " . config::USER_AGENT,
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip",
"DNT: 1",
"Connection: keep-alive",
"Upgrade-Insecure-Requests: 1",
"Sec-Fetch-Dest: document",
"Sec-Fetch-Mode: navigate",
"Sec-Fetch-Site: none",
"Sec-Fetch-User: ?1"]
);
curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
$this->backend->assign_proxy($curlproc, $proxy);
$data = curl_exec($curlproc);
if(curl_errno($curlproc)){
throw new Exception(curl_error($curlproc));
}
curl_close($curlproc);
return $data;
}
public function web($get){
$search = $get["s"];
if(strlen($search) === 0){
throw new Exception("Search term is empty!");
}
$proxy = $this->backend->get_ip();
try{
$json = $this->get(
$proxy,
"https://crowdview-next-js.onrender.com/api/search-v3",
[
"query" => $search
]
);
}catch(Exception $error){
throw new Exception("Failed to fetch JSON");
}
$out = [
"status" => "ok",
"spelling" => [
"type" => "no_correction",
"using" => null,
"correction" => null
],
"npt" => null,
"answer" => [],
"web" => [],
"image" => [],
"video" => [],
"news" => [],
"related" => []
];
$json = json_decode($json, true);
if($json === NULL){
throw new Exception("Failed to decode JSON");
}
foreach($json["results"] as $item){
$description = explode("<b>", $item["snippet"], 2);
$out["web"][] = [
"title" => $this->sanitize($item["title"]),
"description" => $this->sanitize($description[1]),
"url" => $item["link"],
"date" => strtotime($description[0]),
"type" => "web",
"thumb" => [
"url" => null,
"ratio" => null
],
"sublink" => [],
"table" => []
];
}
return $out;
}
private function sanitize($html){
return
trim(
$this->fuckhtml
->getTextContent(
html_entity_decode(
$html
)
),
". "
);
}
}
-309
View File
@@ -1,309 +0,0 @@
<?php
class curlie{
public function __construct(){
include "lib/backend.php";
$this->backend = new backend("curlie");
include "lib/fuckhtml.php";
$this->fuckhtml = new fuckhtml();
}
public function getfilters($page){
if($page != "web"){
return [];
}
return [
"lang" => [
"display" => "Language",
"option" => [
"any" => "Any language",
"en" => "English",
"de" => "German",
"fr" => "French",
"ja" => "Japanese",
"it" => "Italian",
"es" => "Spanish",
"ru" => "Russian",
"nl" => "Dutch",
"pl" => "Polish",
"tr" => "Turkish",
"da" => "Danish",
"sv" => "Swedish",
"no" => "Norwegian",
"is" => "Icelandic",
"fo" => "Faroese",
"fi" => "Finnish",
"et" => "Estonian",
"lt" => "Lithuanian",
"lv" => "Latvian",
"cy" => "Welsh",
"ga" => "Irish",
"gd" => "Scottish Gaelic",
"br" => "Breton",
"fy" => "Frisian",
"frr" => "North Frisian",
"gem" => "Saterland Frisian",
"lb" => "Luxembourgish",
"rm" => "Romansh",
"pt" => "Portuguese",
"ca" => "Catalan",
"gl" => "Galician",
"eu" => "Basque",
"ast" => "Asturian",
"an" => "Aragonese",
"fur" => "Friulan",
"sc" => "Sardinian",
"scn" => "Sicilian",
"oc" => "Occitan",
"be" => "Belarusian",
"cs" => "Czech",
"hu" => "Hungarian",
"sk" => "Slovak",
"uk" => "Ukrainian",
"csb" => "Kashubian",
"tt" => "Tatar",
"ba" => "Bashkir",
"os" => "Ossetian",
"sl" => "Slovene",
"sr" => "Serbian",
"hr" => "Croatian",
"bs" => "Bosnian",
"bg" => "Bulgarian",
"sq" => "Albanian",
"ro" => "Romanian",
"mk" => "Macedonian",
"el" => "Greek",
"iw" => "Hebrew",
"fa" => "Persian",
"ar" => "Arabic",
"ku" => "Kurdish",
"az" => "Azerbaijani",
"hy" => "Armenian",
"af" => "Afrikaans",
"sw" => "Kiswahili",
"uz" => "Uzbek",
"kk" => "Kazakh",
"ky" => "Kyrgyz",
"tg" => "Tajik",
"tk" => "Turkmen",
"ug" => "Uyghurche",
"hi" => "Hindi",
"si" => "Sinhalese",
"gu" => "Gujarati",
"ur" => "Urdu",
"mr" => "Marathi",
"pa" => "Punjabi",
"bn" => "Bengali",
"ta" => "Tamil",
"te" => "Telugu",
"kn" => "Kannada",
"zh_CN" => "Chinese Simplified",
"zh_TW" => "Chinese Traditional",
"ko" => "Korean",
"cfr" => "Taiwanese",
"th" => "Thai",
"vi" => "Vietnamese",
"in" => "Indonesian",
"ms" => "Malay",
"tl" => "Tagalog",
"eo" => "Esperanto",
"ia" => "Interlingua",
"la" => "Latin"
]
]
];
}
private function get($proxy, $url, $get = []){
$curlproc = curl_init();
if($get !== []){
$get = http_build_query($get);
$url .= "?" . $get;
}
curl_setopt($curlproc, CURLOPT_URL, $url);
curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
curl_setopt($curlproc, CURLOPT_HTTPHEADER,
["User-Agent: " . config::USER_AGENT,
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip",
"DNT: 1",
"Connection: keep-alive",
"Upgrade-Insecure-Requests: 1",
"Sec-Fetch-Dest: document",
"Sec-Fetch-Mode: navigate",
"Sec-Fetch-Site: none",
"Sec-Fetch-User: ?1"]
);
curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
$this->backend->assign_proxy($curlproc, $proxy);
$data = curl_exec($curlproc);
if(curl_errno($curlproc)){
throw new Exception(curl_error($curlproc));
}
curl_close($curlproc);
return $data;
}
public function web($get){
if($get["npt"]){
[$query, $proxy] = $this->backend->get($get["npt"], "web");
try{
$html = $this->get(
$proxy,
"https://curlie.org/" . $query,
[]
);
}catch(Exception $error){
throw new Exception("Failed to fetch search page");
}
}else{
$proxy = $this->backend->get_ip();
$query = [
"q" => $get["s"],
"start" => 0,
"stime" => 92452189 // ?
];
if($get["lang"] !== "any"){
$query["lang"] = $get["lang"];
}
try{
$html = $this->get(
$proxy,
"https://curlie.org/search",
$query
);
}catch(Exception $error){
throw new Exception("Failed to fetch search page");
}
}
$this->fuckhtml->load($html);
$nextpage =
$this->fuckhtml
->getElementsByClassName(
"next-page",
"a"
);
if(count($nextpage) !== 0){
$nextpage =
$this->backend->store(
$nextpage[0]["attributes"]["href"],
"web",
$proxy
);
}else{
$nextpage = null;
}
$out = [
"status" => "ok",
"spelling" => [
"type" => "no_correction",
"using" => null,
"correction" => null
],
"npt" => $nextpage,
"answer" => [],
"web" => [],
"image" => [],
"video" => [],
"news" => [],
"related" => []
];
$items =
$this->fuckhtml
->getElementsByClassName(
"site-item",
"div"
);
foreach($items as $item){
$this->fuckhtml->load($item);
$a =
$this->fuckhtml
->getElementsByAttributeValue(
"target",
"_blank",
"a"
)[0];
$description =
$this->fuckhtml
->getElementsByClassName("site-descr");
if(count($description) !== 0){
$description =
$this->fuckhtml
->getTextContent(
$description[0]
);
}else{
$description = null;
}
$out["web"][] = [
"title" =>
$this->fuckhtml
->getTextContent(
$a
),
"description" => $description,
"url" =>
$this->fuckhtml
->getTextContent(
$a["attributes"]["href"]
),
"date" => null,
"type" => "web",
"thumb" => [
"url" => null,
"ratio" => null
],
"sublink" => [],
"table" => []
];
}
return $out;
}
}
+1773 -1825
View File
File diff suppressed because it is too large. Load diff
+327
View File
@@ -0,0 +1,327 @@
<?php
class fivehpx{
public function __construct(){
include "lib/backend.php";
$this->backend = new backend("fivehpx");
include "lib/fuckhtml.php";
$this->fuckhtml = new fuckhtml();
}
public function getfilters($page){
return [
"sort" => [
"display" => "Sort",
"option" => [
"relevance" => "Relevance",
"pulse" => "Pulse",
"newest" => "Newest",
"aqs" => "AI Score"
]
],
"category" => [
"display" => "Category",
"option" => [
"any" => "Any category",
"landscape,nature" => "Landscape",
"city" => "City",
"events,sports" => "Events",
"creative" => "Creative"
]
],/*
"gear" => [
"display" => "Gear",
"option" => [
"any" => "Any gear",
"camera" => "Camera",
"phone" => "Phone"
]
],*/
"downloadable" => [
"display" => "Downloadable",
"option" => [
"any" => "Doesn't matter",
"yes" => "Yes"
]
]
];
}
private function get($proxy, $url, $get = [], $post_data = null){
$curlproc = curl_init();
if($get !== []){
$get = http_build_query($get);
$url .= "?" . $get;
}
curl_setopt($curlproc, CURLOPT_URL, $url);
curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
if($post_data === null){
curl_setopt($curlproc, CURLOPT_HTTPHEADER,
["User-Agent: " . config::USER_AGENT,
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip, deflate, br, zstd",
"DNT: 1",
"Sec-GPC: 1",
"Connection: keep-alive",
"Upgrade-Insecure-Requests: 1",
"Sec-Fetch-Dest: document",
"Sec-Fetch-Mode: navigate",
"Sec-Fetch-Site: same-origin",
"Sec-Fetch-User: ?1",
"Priority: u=0, i",
"TE: trailers"]
);
}else{
curl_setopt($curlproc, CURLOPT_HTTPHEADER,
["User-Agent: " . config::USER_AGENT,
"Accept: application/graphql-response+json,application/json;q=0.9",
"Accept-Language: en-CA,en-US;q=0.9,en;q=0.8",
"Accept-Encoding: gzip, deflate, br, zstd",
"Referer: https://500px.com/",
"content-type: application/json",
"x-500px-platform: Web",
"Content-Length: " . strlen($post_data),
"Origin: https://500px.com",
"DNT: 1",
"Sec-GPC: 1",
"Alt-Used: api-neo.500px.com",
"Connection: keep-alive",
"Cookie: x-500px-device-id-prod=a7b57e48-e477-4382-8c43-70c74415eb96",
"Sec-Fetch-Dest: empty",
"Sec-Fetch-Mode: cors",
"Sec-Fetch-Site: same-site",
"Priority: u=4",
"TE: trailers"]
);
// set post data
curl_setopt($curlproc, CURLOPT_POST, true);
curl_setopt($curlproc, CURLOPT_POSTFIELDS, $post_data);
}
curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
// http2 bypass
curl_setopt($curlproc, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
$this->backend->assign_proxy($curlproc, $proxy);
$data = curl_exec($curlproc);
if(curl_errno($curlproc)){
throw new Exception(curl_error($curlproc));
}
curl_close($curlproc);
return $data;
}
public function image($get){
if($get["npt"]){
[$variables, $proxy] =
$this->backend->get(
$get["npt"], "images"
);
$variables = json_decode($variables, true);
}else{
$search = $get["s"];
if(strlen($search) === 0){
throw new Exception("Search term is empty!");
}
$proxy = $this->backend->get_ip();
$variables = [
"type" => "TEXT",
"sort" => strtoupper($get["sort"]),
"first" => 20,
"keyword" => $search,
"resourceTypes" => ["PHOTO"]
];
if($get["downloadable"] != "any"){
$variables["downloadable"] = "ALLOW";
}
if($get["category"] != "any"){
$variables["categories"] = [strtoupper($get["category"])];
}
/* for some reason this filter doesnt do anything? lol
if($get["gear"] != "any"){
$variables["equipments"] = [strtoupper($get["gear"])];
}*/
}
try{
$json =
$this->get(
$proxy,
"https://api-neo.500px.com/graphql",
[],
json_encode([
"operationName" => "searchResource",
"variables" => $variables,
"extensions" => [
"clientLibrary" => [
"name" => "@apollo/client",
"version" => "4.1.6"
]
],
"query" => "query searchResource(\$keyword: String!, \$type: SearchType = TEXT, \$first: Int!, \$after: String, \$resourceTypes: [SearchResourceType!], \$categories: [String!], \$equipments: [String!], \$downloadable: PhotoDownloadable, \$sort: SearchSortOption = RELEVANCE, \$excludeNsfw: Boolean) {\n searchResource(\n keyword: \$keyword\n type: \$type\n first: \$first\n after: \$after\n resourceTypes: \$resourceTypes\n categories: \$categories\n equipments: \$equipments\n downloadable: \$downloadable\n sort: \$sort\n excludeNsfw: \$excludeNsfw\n ) {\n edges {\n cursor\n node {\n __typename\n ... on Photo {\n id\n isDeleted\n isInReview\n isPrivate\n questInfo {\n rewardQuestName\n __typename\n }\n honors {\n ...PhotoHonorFields\n __typename\n }\n title\n description\n downloadable\n licensing {\n status\n __typename\n }\n urls {\n size_600\n size_1024\n __typename\n }\n uploader {\n id\n username\n avatar\n displayName\n isBlockedByMe\n isFollowedByMe\n isFollowingMe\n membership {\n membership\n __typename\n }\n __typename\n }\n isNsfw\n isLikedByMe\n viewerHasReposted\n width\n height\n dominantColorLight\n dominantColorDark\n hasComment\n uploadedAt\n __typename\n }\n ... on Gallery {\n id\n isDeleted\n isPrivate\n kind\n honors {\n ...PhotoHonorFields\n __typename\n }\n name\n itemCount\n hasComment\n isLikedByMe\n isNsfw\n viewerHasReposted\n creator {\n id\n username\n displayName\n avatar\n isFollowedByMe\n isFollowingMe\n isBlockedByMe\n membership {\n membership\n __typename\n }\n __typename\n }\n coverPhotos(first: 5) {\n id\n urls {\n size_600\n size_1024\n __typename\n }\n __typename\n }\n __typename\n }\n ... on PhotoGroup {\n id\n isDeleted\n isInReview\n isPrivate\n honors {\n ...PhotoHonorFields\n __typename\n }\n title\n description\n numCount\n publicItemCount\n isLikedByMe\n isNsfw\n viewerHasReposted\n uploader {\n id\n username\n displayName\n avatar\n isFollowedByMe\n isFollowingMe\n isBlockedByMe\n membership {\n membership\n __typename\n }\n __typename\n }\n coverPhotos(first: 5) {\n id\n urls {\n size_600\n size_1024\n __typename\n }\n __typename\n }\n __typename\n }\n ... on User {\n id\n username\n displayName\n avatar\n location\n isBlockedByMe\n isFollowedByMe\n isFollowingMe\n membership {\n membership\n __typename\n }\n userCoverPhotos: coverPhotos(first: 6) {\n id\n urls {\n size_1024\n __typename\n }\n __typename\n }\n __typename\n }\n ... on Video {\n id\n isDeleted\n isInReview\n isPrivate\n questInfo {\n rewardQuestName\n __typename\n }\n honors {\n ...PhotoHonorFields\n __typename\n }\n title\n coverUrl\n videoUrl\n width\n height\n isNsfw\n isLikedByMe\n uploader {\n id\n username\n avatar\n displayName\n isBlockedByMe\n isFollowedByMe\n isFollowingMe\n membership {\n membership\n __typename\n }\n __typename\n }\n __typename\n }\n }\n __typename\n }\n pageInfo {\n hasNextPage\n endCursor\n __typename\n }\n totalCount\n __typename\n }\n}\n\nfragment PhotoHonorFields on PhotoHonor {\n __typename\n ... on PhotoHo Line truncated
])
);
}catch(Exception $error){
throw new Exception("Failed to fetch graphQL object");
}
$json = json_decode($json, true);
if($json === null){
throw new Exception("Failed to decode graphQL object");
}
if(isset($json["errors"][0]["message"])){
throw new Exception("500px returned an API error: " . $json["errors"][0]["message"]);
}
if(!isset($json["data"]["searchResource"]["edges"])){
throw new Exception("No edges returned by API");
}
$out = [
"status" => "ok",
"npt" => null,
"image" => []
];
foreach($json["data"]["searchResource"]["edges"] as $image){
$image = $image["node"];
$title =
trim(
$this->fuckhtml
->getTextContent(
$image["title"]
) . ": " .
$this->fuckhtml
->getTextContent(
$image["description"]
)
, " :"
);
$small = $this->image_ratio(600, $image["width"], $image["height"]);
//$large = $this->image_ratio(2048, $image["width"], $image["height"]);
$large = $this->image_ratio(3840, $image["width"], $image["height"]);
// for some fuck ass reason the invalid signature error gives me the
// signature key they use, lol
// signStrPreview:
// 67gfdsgf!5uiyoi3fdsphoto/37070890/0.jpg21790132400
// 67gfdsgf!5uiyoi3fdsphoto/211496303/0.jpg111790132400
// hash("sha256", <-key->photo/<-url id->/0.jpg<-size-><-expiry->)
// https://cdn-resize-prod-com.500px.cloud/photo/211496303/0.jpg?s=1&expiry=1790132400&sig=b2873066126c294d7ca0b6f74db7f2626d79d5337a4c71add910f699734fade2
$images = [];
preg_match(
'/photo\/([0-9]+)/',
$image["urls"]["size_600"],
$image_id
);
if(count($image_id) !== 0){
$expiry = time() + 3600;
$images[] = [
"url" =>
"https://cdn-resize-prod-com.500px.cloud/photo/{$image_id[1]}/0.jpg?s=4&expiry={$expiry}&sig=" .
hash("sha256", "67gfdsgf!5uiyoi3fdsphoto/{$image_id[1]}/0.jpg4{$expiry}"),
"width" => $large[0] > $image["width"] ? $image["width"] : $large[0],
"height" => $large[1] > $image["height"] ? $image["height"] : $large[1]
];
}
// add thumbnail
$images[] = [
"url" => $image["urls"]["size_600"],
"width" => $small[0],
"height" => $small[1]
];
$out["image"][] = [
"title" => $title,
"source" => $images,
"url" => "https://500px.com/photo/{$image["id"]}"
];
}
// get NPT token
if($json["data"]["searchResource"]["pageInfo"]["hasNextPage"] === true){
$variables["after"] = $json["data"]["searchResource"]["pageInfo"]["endCursor"];
$out["npt"] =
$this->backend->store(
json_encode($variables),
"images",
$proxy
);
}
return $out;
}
private function image_ratio($longest_edge, $width, $height){
$ratio = [
$longest_edge / $width,
$longest_edge / $height
];
if($ratio[0] < $ratio[1]){
$ratio = $ratio[0];
}else{
$ratio = $ratio[1];
}
return [
floor($width * $ratio),
floor($height * $ratio)
];
}
}
+415
View File
@@ -0,0 +1,415 @@
<?php
class flickr{
const req_web = 0;
const req_xhr = 1;
public function __construct(){
include "lib/backend.php";
$this->backend = new backend("flickr");
include "lib/fuckhtml.php";
$this->fuckhtml = new fuckhtml();
}
public function getfilters($page){
return [
"nsfw" => [
"display" => "NSFW",
"option" => [
"yes" => "Yes",
"maybe" => "Maybe",
"no" => "No",
]
],
"sort" => [
"display" => "Sort by",
"option" => [
"relevance" => "Relevance",
"date-posted-desc" => "Newest uploads",
"date-posted-asc" => "Oldest uploads",
"date-taken-desc" => "Newest taken",
"date-taken-asc" => "Oldest taken",
"interestingness-desc" => "Interesting"
]
],
"color" => [
"display" => "Color",
"option" => [
"any" => "Any color",
// color_codes=
"0" => "Red",
"1" => "Brown",
"2" => "Orange",
"b" => "Pink",
"4" => "Yellow",
"3" => "Golden",
"5" => "Lime",
"6" => "Green",
"7" => "Sky blue",
"8" => "Blue",
"9" => "Purple",
"a" => "Hot pink",
"c" => "White",
"d" => "Gray",
"e" => "Black",
// styles= override
"blackandwhite" => "Black & white",
]
],
"style" => [ // styles=
"display" => "Style",
"option" => [
"any" => "Any style",
"depthoffield" => "Depth of field",
"minimalism" => "Minimalism",
"pattern" => "Patterns"
]
],
"license" => [
"display" => "License",
"option" => [
"any" => "Any license",
"1,2,3,4,5,6,9,11,12,13,14,15,16" => "All creative commons",
"4,5,6,9,10,11,12,13" => "Commercial use allowed",
"1,2,4,5,9,10,11,12,14,15" => "Modifications allowed",
"4,5,9,10,11,12" => "Commercial use & mods allowed",
"7,9,10" => "No known copyright restrictions",
"8" => "U.S Government works"
]
]
];
}
private function get($proxy, $url, $get = [], $reqtype){
$curlproc = curl_init();
if($get !== []){
$get = http_build_query($get);
$url .= "?" . $get;
}
curl_setopt($curlproc, CURLOPT_URL, $url);
curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
if($reqtype === flickr::req_web){
curl_setopt($curlproc, CURLOPT_HTTPHEADER,
["User-Agent: " . config::USER_AGENT,
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip",
"DNT: 1",
"Sec-GPC: 1",
"Connection: keep-alive",
"Upgrade-Insecure-Requests: 1",
"Sec-Fetch-Dest: document",
"Sec-Fetch-Mode: navigate",
"Sec-Fetch-Site: same-origin",
"Sec-Fetch-User: ?1",
"Priority: u=0, i",
"TE: trailers"]
);
}else{
curl_setopt($curlproc, CURLOPT_HTTPHEADER,
["User-Agent: " . config::USER_AGENT,
"Accept: */*",
"Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip",
"Origin: https://www.flickr.com",
"DNT: 1",
"Sec-GPC: 1",
"Connection: keep-alive",
"Referer: https://www.flickr.com/",
// Cookie:
"Sec-Fetch-Dest: empty",
"Sec-Fetch-Mode: cors",
"Sec-Fetch-Site: same-site",
"TE: trailers"]
);
}
curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
// http2 bypass
curl_setopt($curlproc, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
$this->backend->assign_proxy($curlproc, $proxy);
$data = curl_exec($curlproc);
if(curl_errno($curlproc)){
throw new Exception(curl_error($curlproc));
}
curl_close($curlproc);
return $data;
}
public function image($get){
if($get["npt"]){
[$filters, $proxy] =
$this->backend->get(
$get["npt"], "images"
);
$filters = json_decode($filters, true);
// Workaround for the future, if flickr deprecates &page argument on html page
/*
try{
$json =
$this->get(
$proxy,
"https://api.flickr.com/services/rest",
[
"sort" => $data["sort"],
"parse_tags" => 1,
// url_s,url_n,url_w,url_m,url_z,url_c,url_l,url_h,url_k,url_3k,url_4k,url_5k,url_6k,url_o
"extras" => "can_comment,can_print,count_comments,count_faves,description,isfavorite,license,media,needs_interstitial,owner_name,path_alias,realname,rotation,url_sq,url_q,url_t,url_s,url_n,url_w,url_m,url_z,url_c,url_l",
"per_page" => 100,
"page" => $data["page"],
"lang" => "en-US",
"text" => $data["search"],
"viewerNSID" => "",
"method" => "flickr.photos.search",
"csrf" => "",
"api_key" => $data["api_key"],
"format" => "json",
"hermes" => 1,
"hermesClient" => 1,
"reqId" => $data["reqId"],
"nojsoncallback" => 1
]
);
}catch(Exception $error){
throw new Exception("Failed to fetch JSON");
}*/
}else{
if(strlen($get["s"]) === 0){
throw new Exception("Search term is empty!");
}
$proxy = $this->backend->get_ip();
// compute filters
$filters = [
"page" => 1,
"sort" => $get["sort"]
];
if($get["style"] != "any"){
$filters["styles"] = $get["style"];
}
if($get["color"] != "any"){
if($get["color"] != "blackandwhite"){
$filters["color_codes"] = $get["color"];
}else{
$filters["styles"] = "blackandwhite";
}
}
if($get["license"] != "any"){
$filters["license"] = $get["license"];
}
switch($get["nsfw"]){
case "yes": $filters["safe_search"] = 0; break;
case "maybe": $filters["safe_search"] = 2; break;
case "no": $filters["safe_search"] = 1; break;
}
}
$get_params = [
"text" => $get["s"],
"per_page" => 50,
// scrape highest resolution
"extras" => "url_s,url_n,url_w,url_m,url_z,url_c,url_l,url_h,url_k,url_3k,url_4k,url_5k,url_6k,url_o",
"view_all" => 1
];
$get_params = array_merge($get_params, $filters);
$html =
$this->get(
$proxy,
"https://www.flickr.com/search/",
$get_params,
flickr::req_web
);
// @TODO
// get api_key and reqId, if flickr deprecates &page
$this->fuckhtml->load($html);
//
// get response JSON
//
$scripts =
$this->fuckhtml
->getElementsByClassName(
"modelExport",
"script"
);
$found = false;
foreach($scripts as $script){
$json =
preg_split(
'/modelExport: ?/',
$script["innerHTML"],
2
);
if(count($json) !== 0){
$found = true;
$json = $json[1];
break;
}
}
if($found === false){
throw new Exception("Failed to grep JSON");
}
$json =
json_decode(
$this->fuckhtml
->extract_json(
$json
),
true
);
if($json === null){
throw new Exception("Failed to decode JSON");
}
$out = [
"status" => "ok",
"npt" => null,
"image" => []
];
if(!isset($json["main"]["search-photos-lite-models"][0]["data"]["photos"]["data"]["_data"])){
throw new Exception("Failed to access data object");
}
foreach($json["main"]["search-photos-lite-models"][0]["data"]["photos"]["data"]["_data"] as $image){
if(!isset($image["data"])){
// flickr likes to gives us empty array objects
continue;
}
$image = $image["data"];
$title = [];
if(isset($image["title"])){
$title[] =
$this->fuckhtml
->getTextContent(
$image["title"]
);
}
if(isset($image["description"])){
$title[] =
$this->fuckhtml
->getTextContent(
str_replace(
"\n",
" ",
$image["description"]
)
);
}
$title = implode(": ", $title);
$sources = array_values($image["sizes"]["data"]);
$suitable_sizes = ["n", "m", "w", "s"];
$thumb = &$sources[0]["data"];
foreach($suitable_sizes as $testing_size){
if(isset($image["sizes"]["data"][$testing_size])){
$thumb = &$image["sizes"]["data"][$testing_size]["data"];
break;
}
}
$og = &$sources[count($sources) - 1]["data"];
$out["image"][] = [
"title" => $title,
"source" => [
[
"url" => "https:" . $og["displayUrl"],
"width" => (int)$og["width"],
"height" => (int)$og["height"]
],
[
"url" => "https:" . $thumb["displayUrl"],
"width" => (int)$thumb["width"],
"height" => (int)$thumb["height"]
]
],
"url" => "https://www.flickr.com/photos/" . $image["ownerNsid"] . "/" . $image["id"] . "/"
];
}
$total_items = (int)$json["main"]["search-photos-lite-models"][0]["data"]["photos"]["data"]["totalItems"];
if(($filters["page"]) * 50 < $total_items){
$filters["page"]++;
$out["npt"] =
$this->backend->store(
json_encode($filters),
"images",
$proxy
);
}
return $out;
}
}
+1 -1
View File
@@ -136,7 +136,7 @@ class ftm{
"source" => [ "source" => [
[ [
"url" => "url" =>
"https://findthatmeme.us-southeast-1.linodeobjects.com/" . "https://s3.thehackerblog.com/findthatmeme/" .
$thumb, $thumb,
"width" => null, "width" => null,
"height" => null "height" => null
-320
View File
@@ -1,320 +0,0 @@
<?php
class ghostery{
public function __construct(){
include "lib/backend.php";
$this->backend = new backend("ghostery");
include "lib/fuckhtml.php";
$this->fuckhtml = new fuckhtml();
}
public function getfilters($page){
if($page != "web"){
return [];
}
return [
"country" => [
"display" => "Country",
"option" => [
"any" => "All regions",
"AR" => "Argentina",
"AU" => "Australia",
"AT" => "Austria",
"BE" => "Belgium",
"BR" => "Brazil",
"CA" => "Canada",
"CL" => "Chile",
"DK" => "Denmark",
"FI" => "Finland",
"FR" => "France",
"DE" => "Germany",
"HK" => "Hong Kong",
"IN" => "India",
"ID" => "Indonesia",
"IT" => "Italy",
"JP" => "Japan",
"KR" => "Korea",
"MY" => "Malaysia",
"MX" => "Mexico",
"NL" => "Netherlands",
"NZ" => "New Zealand",
"NO" => "Norway",
"CN" => "People's Republic of China",
"PL" => "Poland",
"PT" => "Portugal",
"PH" => "Republic of the Philippines",
"RU" => "Russia",
"SA" => "Saudi Arabia",
"ZA" => "South Africa",
"ES" => "Spain",
"SE" => "Sweden",
"CH" => "Switzerland",
"TW" => "Taiwan",
"TR" => "Turkey",
"GB" => "United Kingdom",
"US" => "United States"
]
]
];
}
private function get($proxy, $url, $get = [], $country){
$curlproc = curl_init();
if($get !== []){
$get = http_build_query($get);
$url .= "?" . $get;
}
curl_setopt($curlproc, CURLOPT_URL, $url);
curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
curl_setopt($curlproc, CURLOPT_HTTPHEADER,
["User-Agent: " . config::USER_AGENT,
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip",
"Referer: https://ghosterysearch.com",
"DNT: 1",
"Sec-GPC: 1",
"Connection: keep-alive",
"Cookie: ctry=" . ($country == "any" ? "--" : $country) . "; noads=true",
"Upgrade-Insecure-Requests: 1",
"Sec-Fetch-Dest: document",
"Sec-Fetch-Mode: navigate",
"Sec-Fetch-Site: same-origin",
"Sec-Fetch-User: ?1",
"Priority: u=0, i"]
);
// http2 bypass
curl_setopt($curlproc, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
$this->backend->assign_proxy($curlproc, $proxy);
$data = curl_exec($curlproc);
if(curl_errno($curlproc)){
throw new Exception(curl_error($curlproc));
}
curl_close($curlproc);
return $data;
}
public function web($get){
if($get["npt"]){
[$query, $proxy] = $this->backend->get($get["npt"], "web");
parse_str($query, $query);
// country
$country = $query["c"];
unset($query["c"]);
$query = http_build_query($query);
try{
$html =
$this->get(
$proxy,
"https://ghosterysearch.com/search?" . $query,
[],
$country
);
}catch(Exception $error){
throw new Exception("Failed to fetch search page");
}
}else{
$proxy = $this->backend->get_ip();
try{
$html =
$this->get(
$proxy,
"https://ghosterysearch.com/search",
[
"q" => $get["s"]
],
$get["country"]
);
}catch(Exception $error){
throw new Exception("Failed to fetch search page");
}
}
$out = [
"status" => "ok",
"spelling" => [
"type" => "no_correction",
"using" => null,
"correction" => null
],
"npt" => null,
"answer" => [],
"web" => [],
"image" => [],
"video" => [],
"news" => [],
"related" => []
];
$this->fuckhtml->load($html);
$results_wrapper =
$this->fuckhtml
->getElementsByClassName(
"results",
"section"
);
if(count($results_wrapper) === 0){
throw new Exception("Failed to grep result section");
}
$this->fuckhtml->load($results_wrapper[0]);
// get search results
$results =
$this->fuckhtml
->getElementsByClassName(
"result",
"li"
);
if(count($results) === 0){
return $out;
}
foreach($results as $result){
$this->fuckhtml->load($result);
$a =
$this->fuckhtml
->getElementsByClassName(
"url",
"a"
);
if(count($a) === 0){
continue;
}
$a = $a[0];
$out["web"][] = [
"title" =>
$this->titledots(
$this->fuckhtml
->getTextContent(
$this->fuckhtml
->getElementsByTagName(
"h2"
)[0]
)
),
"description" =>
$this->titledots(
$this->fuckhtml
->getTextContent(
$this->fuckhtml
->getElementsByTagName(
"p"
)[0]
)
),
"url" =>
$this->fuckhtml
->getTextContent(
$a
["attributes"]
["href"]
),
"date" => null,
"type" => "web",
"thumb" => [
"url" => null,
"ratio" => null
],
"sublink" => [],
"table" => []
];
}
$this->fuckhtml->load($html);
// get pagination token
$pagination_wrapper =
$this->fuckhtml
->getElementsByClassName(
"pagination",
"div"
);
if(count($pagination_wrapper) !== 0){
// found next page!
$this->fuckhtml->load($pagination_wrapper[0]);
$a =
$this->fuckhtml
->getElementsByTagName(
"a"
);
if(count($a) !== 0){
$q =
parse_url(
$this->fuckhtml
->getTextContent(
$a[count($a) - 1]
["attributes"]
["href"]
),
PHP_URL_QUERY
);
$out["npt"] =
$this->backend
->store(
$q . "&c=" . $get["country"],
"web",
$proxy
);
}
}
return $out;
}
private function titledots($title){
return trim($title, " .\t\n\r\0\x0B…");
}
}
+1872 -3591
View File
File diff suppressed because it is too large. Load diff
+872
View File
@@ -0,0 +1,872 @@
<?php
// @TODO check for consent.google.com page, if need be
class google_api{
public function __construct(){
include "lib/backend.php";
$this->backend = new backend("google_api");
}
public function getfilters($page){
$base = [
"country" => [ // gl=<country> (image: cr=countryAF)
"display" => "Country",
"option" => [
"any" => "Instance's country",
"af" => "Afghanistan",
"al" => "Albania",
"dz" => "Algeria",
"as" => "American Samoa",
"ad" => "Andorra",
"ao" => "Angola",
"ai" => "Anguilla",
"aq" => "Antarctica",
"ag" => "Antigua and Barbuda",
"ar" => "Argentina",
"am" => "Armenia",
"aw" => "Aruba",
"au" => "Australia",
"at" => "Austria",
"az" => "Azerbaijan",
"bs" => "Bahamas",
"bh" => "Bahrain",
"bd" => "Bangladesh",
"bb" => "Barbados",
"by" => "Belarus",
"be" => "Belgium",
"bz" => "Belize",
"bj" => "Benin",
"bm" => "Bermuda",
"bt" => "Bhutan",
"bo" => "Bolivia",
"ba" => "Bosnia and Herzegovina",
"bw" => "Botswana",
"bv" => "Bouvet Island",
"br" => "Brazil",
"io" => "British Indian Ocean Territory",
"bn" => "Brunei Darussalam",
"bg" => "Bulgaria",
"bf" => "Burkina Faso",
"bi" => "Burundi",
"kh" => "Cambodia",
"cm" => "Cameroon",
"ca" => "Canada",
"cv" => "Cape Verde",
"ky" => "Cayman Islands",
"cf" => "Central African Republic",
"td" => "Chad",
"cl" => "Chile",
"cn" => "China",
"cx" => "Christmas Island",
"cc" => "Cocos (Keeling) Islands",
"co" => "Colombia",
"km" => "Comoros",
"cg" => "Congo",
"cd" => "Congo, the Democratic Republic",
"ck" => "Cook Islands",
"cr" => "Costa Rica",
"ci" => "Cote D'ivoire",
"hr" => "Croatia",
"cu" => "Cuba",
"cy" => "Cyprus",
"cz" => "Czech Republic",
"dk" => "Denmark",
"dj" => "Djibouti",
"dm" => "Dominica",
"do" => "Dominican Republic",
"ec" => "Ecuador",
"eg" => "Egypt",
"sv" => "El Salvador",
"gq" => "Equatorial Guinea",
"er" => "Eritrea",
"ee" => "Estonia",
"et" => "Ethiopia",
"fk" => "Falkland Islands (Malvinas)",
"fo" => "Faroe Islands",
"fj" => "Fiji",
"fi" => "Finland",
"fr" => "France",
"gf" => "French Guiana",
"pf" => "French Polynesia",
"tf" => "French Southern Territories",
"ga" => "Gabon",
"gm" => "Gambia",
"ge" => "Georgia",
"de" => "Germany",
"gh" => "Ghana",
"gi" => "Gibraltar",
"gr" => "Greece",
"gl" => "Greenland",
"gd" => "Grenada",
"gp" => "Guadeloupe",
"gu" => "Guam",
"gt" => "Guatemala",
"gn" => "Guinea",
"gw" => "Guinea-Bissau",
"gy" => "Guyana",
"ht" => "Haiti",
"hm" => "Heard Island and Mcdonald Islands",
"va" => "Holy See (Vatican City State)",
"hn" => "Honduras",
"hk" => "Hong Kong",
"hu" => "Hungary",
"is" => "Iceland",
"in" => "India",
"id" => "Indonesia",
"ir" => "Iran, Islamic Republic",
"iq" => "Iraq",
"ie" => "Ireland",
"il" => "Israel",
"it" => "Italy",
"jm" => "Jamaica",
"jp" => "Japan",
"jo" => "Jordan",
"kz" => "Kazakhstan",
"ke" => "Kenya",
"ki" => "Kiribati",
"kp" => "Korea, Democratic People's Republic",
"kr" => "Korea, Republic",
"kw" => "Kuwait",
"kg" => "Kyrgyzstan",
"la" => "Lao People's Democratic Republic",
"lv" => "Latvia",
"lb" => "Lebanon",
"ls" => "Lesotho",
"lr" => "Liberia",
"ly" => "Libyan Arab Jamahiriya",
"li" => "Liechtenstein",
"lt" => "Lithuania",
"lu" => "Luxembourg",
"mo" => "Macao",
"mk" => "Macedonia, the Former Yugosalv Republic",
"mg" => "Madagascar",
"mw" => "Malawi",
"my" => "Malaysia",
"mv" => "Maldives",
"ml" => "Mali",
"mt" => "Malta",
"mh" => "Marshall Islands",
"mq" => "Martinique",
"mr" => "Mauritania",
"mu" => "Mauritius",
"yt" => "Mayotte",
"mx" => "Mexico",
"fm" => "Micronesia, Federated States",
"md" => "Moldova, Republic",
"mc" => "Monaco",
"mn" => "Mongolia",
"ms" => "Montserrat",
"ma" => "Morocco",
"mz" => "Mozambique",
"mm" => "Myanmar",
"na" => "Namibia",
"nr" => "Nauru",
"np" => "Nepal",
"nl" => "Netherlands",
"an" => "Netherlands Antilles",
"nc" => "New Caledonia",
"nz" => "New Zealand",
"ni" => "Nicaragua",
"ne" => "Niger",
"ng" => "Nigeria",
"nu" => "Niue",
"nf" => "Norfolk Island",
"mp" => "Northern Mariana Islands",
"no" => "Norway",
"om" => "Oman",
"pk" => "Pakistan",
"pw" => "Palau",
"ps" => "Palestinian Territory, Occupied",
"pa" => "Panama",
"pg" => "Papua New Guinea",
"py" => "Paraguay",
"pe" => "Peru",
"ph" => "Philippines",
"pn" => "Pitcairn",
"pl" => "Poland",
"pt" => "Portugal",
"pr" => "Puerto Rico",
"qa" => "Qatar",
"re" => "Reunion",
"ro" => "Romania",
"ru" => "Russian Federation",
"rw" => "Rwanda",
"sh" => "Saint Helena",
"kn" => "Saint Kitts and Nevis",
"lc" => "Saint Lucia",
"pm" => "Saint Pierre and Miquelon",
"vc" => "Saint Vincent and the Grenadines",
"ws" => "Samoa",
"sm" => "San Marino",
"st" => "Sao Tome and Principe",
"sa" => "Saudi Arabia",
"sn" => "Senegal",
"cs" => "Serbia and Montenegro",
"sc" => "Seychelles",
"sl" => "Sierra Leone",
"sg" => "Singapore",
"sk" => "Slovakia",
"si" => "Slovenia",
"sb" => "Solomon Islands",
"so" => "Somalia",
"za" => "South Africa",
"gs" => "South Georgia and the South Sandwich Islands",
"es" => "Spain",
"lk" => "Sri Lanka",
"sd" => "Sudan",
"sr" => "Suriname",
"sj" => "Svalbard and Jan Mayen",
"sz" => "Swaziland",
"se" => "Sweden",
"ch" => "Switzerland",
"sy" => "Syrian Arab Republic",
"tw" => "Taiwan, Province of China",
"tj" => "Tajikistan",
"tz" => "Tanzania, United Republic",
"th" => "Thailand",
"tl" => "Timor-Leste",
"tg" => "Togo",
"tk" => "Tokelau",
"to" => "Tonga",
"tt" => "Trinidad and Tobago",
"tn" => "Tunisia",
"tr" => "Turkey",
"tm" => "Turkmenistan",
"tc" => "Turks and Caicos Islands",
"tv" => "Tuvalu",
"ug" => "Uganda",
"ua" => "Ukraine",
"ae" => "United Arab Emirates",
"uk" => "United Kingdom",
"us" => "United States",
"um" => "United States Minor Outlying Islands",
"uy" => "Uruguay",
"uz" => "Uzbekistan",
"vu" => "Vanuatu",
"ve" => "Venezuela",
"vn" => "Viet Nam",
"vg" => "Virgin Islands, British",
"vi" => "Virgin Islands, U.S.",
"wf" => "Wallis and Futuna",
"eh" => "Western Sahara",
"ye" => "Yemen",
"zm" => "Zambia",
"zw" => "Zimbabwe"
]
],
"nsfw" => [
"display" => "NSFW",
"option" => [
"yes" => "Yes", // safe=active
"no" => "No" // safe=off
]
],
"sort" => [ // sort
"display" => "Sort by",
"option" => [
"any" => "Any order",
"date:d" => "Oldest",
"date:a" => "Newest"
]
],
"newer" => [
"display" => "Newer than",
"option" => "_DATE"
],
"rm_dupes" => [ // filter
"display" => "Remove duplicates",
"option" => [
"yes" => "Yes", // 1
"no" => "No" // 0
]
]
];
switch($page){
case "web":
return array_merge(
$base,
[
"lang" => [ // lr=<lang> (prefix lang with "lang_")
"display" => "Language",
"option" => [
"any" => "Any language",
"ar" => "Arabic",
"bg" => "Bulgarian",
"ca" => "Catalan",
"cs" => "Czech",
"da" => "Danish",
"de" => "German",
"el" => "Greek",
"en" => "English",
"es" => "Spanish",
"et" => "Estonian",
"fi" => "Finnish",
"fr" => "French",
"hr" => "Croatian",
"hu" => "Hungarian",
"id" => "Indonesian",
"is" => "Icelandic",
"it" => "Italian",
"iw" => "Hebrew",
"ja" => "Japanese",
"ko" => "Korean",
"lt" => "Lithuanian",
"lv" => "Latvian",
"nl" => "Dutch",
"no" => "Norwegian",
"pl" => "Polish",
"pt" => "Portuguese",
"ro" => "Romanian",
"ru" => "Russian",
"sk" => "Slovak",
"sl" => "Slovenian",
"sr" => "Serbian",
"sv" => "Swedish",
"tr" => "Turkish",
"zh-CN" => "Chinese (Simplified)",
"zh-TW" => "Chinese (Traditional)"
]
]
]
);
break;
case "images":
return array_merge(
$base,
[
"size" => [ // imgSize
"display" => "Size",
"option" => [
"any" => "Any size",
"icon" => "Icon",
"small" => "Small",
"medium" => "Medium",
"large" => "Large",
"xlarge" => "X-Large",
"xxlarge" => "XX-Large",
"huge" => "Huge"
]
],
"format" => [ // fileType
"display" => "Format",
"option" => [
"any" => "Any format",
"jpg" => "JPG",
"gif" => "GIF",
"png" => "PNG",
"bmp" => "BMP",
"svg" => "SVG",
"webp" => "WEBP",
"ico" => "ICO",
"craw" => "RAW"
]
],
"color" => [
"display" => "Color",
"option" => [
"any" => "Any color",
"color" => "Full color", // imgColorType
"mono" => "Black & White",
"trans" => "Transparent background",
"red" => "Red", // imgDominantColor
"orange" => "Orange",
"yellow" => "Yellow",
"green" => "Green",
"teal" => "Teal",
"blue" => "Blue",
"purple" => "Purple",
"pink" => "Pink",
"white" => "White",
"gray" => "Gray",
"black" => "Black",
"brown" => "Brown"
]
],
"type" => [ // imgType
"display" => "Type",
"option" => [
"any" => "Any type",
"clipart" => "Clip Art",
"face" => "Faces",
"lineart" => "Line Drawing",
"stock" => "Stock photos",
"photo" => "Photos",
"animated" => "Animated",
]
],
"rights" => [ // rights
"display" => "Usage rights",
"option" => [
"any" => "Any license",
"cc_publicdomain" => "Public domain",
"cc_attribute" => "Attribution required",
"cc_sharealike" => "Sharealike",
"cc_noncommercial" => "Non-commercial use only",
"cc_nonderived" => "Original works"
]
]
]
);
break;
}
}
private function get($proxy, $url, $get = []){
$curlproc = curl_init();
$headers = [
"Accept: application/json",
"Accept-Encoding: gzip"
];
if($get !== []){
$get = http_build_query($get);
$url .= "?" . $get;
}
curl_setopt($curlproc, CURLOPT_URL, $url);
curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
curl_setopt($curlproc, CURLOPT_HTTPHEADER, $headers);
curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
// follow redirects
curl_setopt($curlproc, CURLOPT_FOLLOWLOCATION, true);
$this->backend->assign_proxy($curlproc, $proxy);
$data = curl_exec($curlproc);
if(curl_errno($curlproc)){
throw new Exception(curl_error($curlproc));
}
curl_close($curlproc);
return $data;
}
public function web($get){
// rotate proxy + key on EVERY request
$keydata = $this->backend->get_key();
$proxy = $this->backend->get_ip($keydata["increment"]);
if($get["npt"]){
// $p is never used
[$params, $p] = $this->backend->get(
$get["npt"],
"web"
);
$params = json_decode($params, true);
$params["key"] = $keydata["key"];
}else{
//$json = file_get_contents("scraper/google.json");
$params = [
"q" => $get["s"],
"cx" => config::GOOGLE_CX_ENDPOINT,
"num" => 10,
"start" => 1,
"key" => $keydata["key"]
];
//
// parse filters
//
if($get["newer"] !== false){
$params["dateRestrict"] = "d" . (round((time() - $get["newer"]) / 100000));
}
if($get["rm_dupes"] == "no"){ $params["filter"] = "0"; }
if($get["country"] != "any"){ $params["gl"] = $get["country"]; }
if($get["lang"] != "any"){ $params["lr"] = "lang_" . $get["lang"]; }
if($get["nsfw"] == "yes"){
$params["safe"] = "off";
}else{
$params["safe"] = "active";
}
if($get["sort"] != "any"){ $params["sort"] = $get["sort"]; }
}
try{
$json =
$this->get(
$proxy,
"https://www.googleapis.com/customsearch/v1",
$params
);
}catch(Exception $error){
throw new Exception("Failed to fetch JSON");
}
$json = json_decode($json, true);
if($json === null){
throw new Exception("Failed to decode JSON");
}
$out = [
"status" => "ok",
"spelling" => [
"type" => "no_correction",
"using" => null,
"correction" => null
],
"npt" => null,
"answer" => [],
"web" => [],
"image" => [],
"video" => [],
"news" => [],
"related" => []
];
if(isset($json["error"]["message"])){
throw new Exception(
"API returned an error: " .
$json["error"]["message"] .
" (key #" . $keydata["increment"] . ")"
);
}
if(!isset($json["items"])){
// google just doesnt return items when theres no results
return $out;
}
foreach($json["items"] as $result){
$date = null;
//
// probe for thumbnail
//
$probes = [
isset($result["pagemap"]["cse_thumbnail"][0]["src"]) ? $result["pagemap"]["cse_thumbnail"][0]["src"] : null,
isset($result["pagemap"]["cse_image"][0]["src"]) ? $result["pagemap"]["cse_image"][0]["src"] : null,
isset($result["pagemap"]["metatags"][0]["twitter:image"]) ? $result["pagemap"]["metatags"][0]["twitter:image"] : null,
isset($result["pagemap"]["metatags"][0]["og:image"]) ? $result["pagemap"]["metatags"][0]["og:image"] : null
];
$thumb = [
"url" => null,
"ratio" => null
];
foreach($probes as $probe){
if($probe !== null){
$thumb = [
"url" => $probe,
"ratio" => "16:9"
];
break;
}
}
//
// probe for page format
//
$mime = "web";
if(isset($result["mime"])){
$result["mime"] =
explode(
"/",
$result["mime"],
2
);
if(count($result["mime"]) === 2){
$mime = strtoupper($result["mime"][1]);
}
}
$description = $result["snippet"];
//
// Get date
//
$description_split =
explode(
"...", $description, 2
);
if(count($description_split) === 1){
$description = $result["snippet"];
}elseif(strlen($description_split[0]) < 17){
$date = trim($description_split[0]);
$date_probe = strtotime($date);
if($date_probe !== false){
$description = $description_split[1];
}else{
//
// fallback to getting date from meta tags
//
if(isset($result["pagemap"]["metatags"][0]["creationdate"])){
$date = $result["pagemap"]["metatags"][0]["creationdate"];
}elseif(isset($result["pagemap"]["metatags"][0]["moddate"])){
$date = $result["pagemap"]["metatags"][0]["moddate"];
}else{
$date = null;
}
$description = $result["snippet"];
}
}
if($date !== null){
$date =
strtotime(
trim(
str_replace(
["D:", "'"],
"",
$date
)
)
);
if($date === false){
$date = null;
}
}
$out["web"][] = [
"title" =>
$this->titledots(
$result["title"]
),
"description" =>
$this->titledots(
$description
),
"url" => $result["link"],
"date" => $date,
"type" => $mime,
"thumb" => $thumb,
"sublink" => [],
"table" => []
];
}
// get npt
if(isset($json["queries"]["nextPage"][0]["startIndex"])){
unset($params["key"]);
$params["start"] = (int)$json["queries"]["nextPage"][0]["startIndex"];
$out["npt"] =
$this->backend->store(
json_encode($params),
"web",
$proxy
);
}
return $out;
}
public function image($get){
// rotate proxy + key on EVERY request
$keydata = $this->backend->get_key();
$proxy = $this->backend->get_ip($keydata["increment"]);
if($get["npt"]){
// $p is never used
[$params, $p] = $this->backend->get(
$get["npt"],
"web"
);
$params = json_decode($params, true);
$params["key"] = $keydata["key"];
}else{
//$json = file_get_contents("scraper/google.json");
$params = [
"q" => $get["s"],
"cx" => config::GOOGLE_CX_ENDPOINT,
"num" => 10,
"start" => 1,
"searchType" => "image",
"key" => $keydata["key"]
];
//
// parse filters
//
if($get["newer"] !== false){
$params["dateRestrict"] = "d" . (round((time() - $get["newer"]) / 100000));
}
if($get["rm_dupes"] == "no"){ $params["filter"] = "0"; }
if($get["country"] != "any"){ $params["gl"] = $get["country"]; }
if($get["nsfw"] == "yes"){
$params["safe"] = "off";
}else{
$params["safe"] = "active";
}
if($get["sort"] != "any"){ $params["sort"] = $get["sort"]; }
// image filters
if($get["size"] != "any"){ $params["imgSize"] = $get["size"]; }
if($get["format"] != "any"){ $params["fileType"] = $get["format"]; }
switch($get["color"]){
case "any":
break;
case "color":
case "mono":
case "trans":
$params["imgColorType"] = $get["color"];
break;
default:
$params["imgDominantColor"] = $get["color"];
break;
}
if($get["type"] != "any"){ $params["imgType"] = $get["type"]; }
if($get["rights"] != "any"){ $params["rights"] = $get["rights"]; }
}
try{
$json =
$this->get(
$proxy,
"https://www.googleapis.com/customsearch/v1",
$params
);
}catch(Exception $error){
throw new Exception("Failed to fetch JSON");
}
$json = json_decode($json, true);
if($json === null){
throw new Exception("Failed to decode JSON");
}
$out = [
"status" => "ok",
"npt" => null,
"image" => []
];
if(isset($json["error"]["message"])){
throw new Exception(
"API returned an error: " .
$json["error"]["message"] .
" (key #" . $keydata["increment"] . ")"
);
}
if(!isset($json["items"])){
// google just doesnt return items when theres no results
return $out;
}
foreach($json["items"] as $image){
$out["image"][] = [
"title" => $this->titledots($image["title"]),
"source" => [
[
"url" => $image["link"],
"width" => (int)$image["image"]["width"],
"height" => (int)$image["image"]["height"]
],
[
"url" => $image["image"]["thumbnailLink"],
"width" => (int)$image["image"]["thumbnailWidth"],
"height" => (int)$image["image"]["thumbnailHeight"]
]
],
"url" => $image["image"]["contextLink"]
];
}
// get npt
if(isset($json["queries"]["nextPage"][0]["startIndex"])){
unset($params["key"]);
$params["start"] = (int)$json["queries"]["nextPage"][0]["startIndex"];
$out["npt"] =
$this->backend->store(
json_encode($params),
"web",
$proxy
);
}
return $out;
}
private function titledots($title){
return trim($title, " .\t\n\r\0\x0B…");
}
}
File diff suppressed because it is too large. Load diff
-435
View File
@@ -1,435 +0,0 @@
<?php
class greppr{
public function __construct(){
include "lib/backend.php";
$this->backend = new backend("greppr");
include "lib/fuckhtml.php";
$this->fuckhtml = new fuckhtml();
}
public function getfilters($page){
return [];
}
private function get($proxy, $url, $get = [], $cookie = false){
$curlproc = curl_init();
if($get !== []){
$get = http_build_query($get);
$url .= "?" . $get;
}
curl_setopt($curlproc, CURLOPT_URL, $url);
curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
if($cookie === false){
curl_setopt($curlproc, CURLOPT_HTTPHEADER,
["User-Agent: " . config::USER_AGENT,
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip",
"DNT: 1",
"Connection: keep-alive",
"Upgrade-Insecure-Requests: 1",
"Sec-Fetch-Dest: document",
"Sec-Fetch-Mode: navigate",
"Sec-Fetch-Site: none",
"Sec-Fetch-User: ?1"]
);
}else{
curl_setopt($curlproc, CURLOPT_HTTPHEADER,
["User-Agent: " . config::USER_AGENT,
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip",
"Cookie: PHPSESSID=" . $cookie,
"DNT: 1",
"Connection: keep-alive",
"Upgrade-Insecure-Requests: 1",
"Sec-Fetch-Dest: document",
"Sec-Fetch-Mode: navigate",
"Sec-Fetch-Site: none",
"Sec-Fetch-User: ?1"]
);
}
curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
$this->backend->assign_proxy($curlproc, $proxy);
$headers = [];
curl_setopt(
$curlproc,
CURLOPT_HEADERFUNCTION,
function($curlproc, $header) use (&$headers){
$len = strlen($header);
$header = explode(':', $header, 2);
if(count($header) < 2){
// ignore invalid headers
return $len;
}
$headers[strtolower(trim($header[0]))] = trim($header[1]);
return $len;
}
);
$data = curl_exec($curlproc);
if(curl_errno($curlproc)){
throw new Exception(curl_error($curlproc));
}
curl_close($curlproc);
return [
"headers" => $headers,
"data" => $data
];
}
public function web($get, $first_attempt = true){
if($get["npt"]){
[$q, $proxy] = $this->backend->get($get["npt"], "web");
$q = json_decode($q, true);
}else{
$search = $get["s"];
if(strlen($search) === 0){
throw new Exception("Search term is empty!");
}
$proxy = $this->backend->get_ip();
}
// get token
// token[0] = static token that changes once a day
// token[1] = dynamic token that changes on every request
// token[1] = PHPSESSID cookie
$tokens = apcu_fetch("greppr_token");
if(
$tokens === false ||
$first_attempt === false // force token fetch
){
// we haven't gotten the token yet, get it
try{
$response =
$this->get(
$proxy,
"https://greppr.org",
[]
);
}catch(Exception $error){
throw new Exception("Failed to fetch search tokens");
}
$tokens = $this->parse_token($response);
if($tokens === false){
throw new Exception("Failed to grep search tokens");
}
}
try{
if($get["npt"]){
$params = [
$tokens[0] => $q["q"],
"s" => $q["s"],
"l" => 30,
"n" => $tokens[1]
];
}else{
$params = [
$tokens[0] => $search,
"n" => $tokens[1]
];
}
$searchresults = $this->get(
$proxy,
"https://greppr.org/search",
$params,
$tokens[2]
);
}catch(Exception $error){
throw new Exception("Failed to fetch search page");
}
if(strlen($searchresults["data"]) === 0){
// redirected to main page, which means we got old token
// generate a new one
// ... unless we just tried to do that
if($first_attempt === false){
throw new Exception("Failed to get a new search token");
}
return $this->web($get, false);
}
// refresh the token with new data (this also triggers fuckhtml load)
$this->parse_token($searchresults, $tokens[2]);
// response object
$out = [
"status" => "ok",
"spelling" => [
"type" => "no_correction",
"using" => null,
"correction" => null
],
"npt" => null,
"answer" => [],
"web" => [],
"image" => [],
"video" => [],
"news" => [],
"related" => []
];
// get results for later
$results =
$this->fuckhtml
->getElementsByClassName(
"result",
"div"
);
// check for next page
$next_elem =
$this->fuckhtml
->getElementsByClassName(
"pagination",
"ul"
);
if(count($next_elem) !== 0){
$this->fuckhtml->load($next_elem[0]);
$as =
$this->fuckhtml
->getElementsByClassName(
"page-link",
"a"
);
$break = false;
foreach($as as $a){
if($break === true){
parse_str(
$this->fuckhtml
->getTextContent(
$a["attributes"]["href"]
),
$values
);
$values = array_values($values);
$out["npt"] =
$this->backend->store(
json_encode(
[
"q" => $values[0],
"s" => $values[1]
]
),
"web",
$proxy
);
break;
}
if($a["attributes"]["href"] == "#"){
$break = true;
}
}
}
// scrape results
foreach($results as $result){
$this->fuckhtml->load($result);
$a =
$this->fuckhtml
->getElementsByTagName(
"a"
)[0];
$description =
$this->fuckhtml
->getElementsByClassName(
"highlightedDesc",
"p"
);
if(count($description) === 0){
$description = null;
}else{
$description =
$this->limitstrlen(
$this->fuckhtml
->getTextContent(
$description[0]
)
);
}
$date =
$this->fuckhtml
->getElementsByTagName(
"p"
);
$date =
strtotime(
explode(
":",
$this->fuckhtml
->getTextContent(
$date[count($date) - 1]["innerHTML"]
)
)[1]
);
$out["web"][] = [
"title" =>
$this->fuckhtml
->getTextContent(
$a["innerHTML"]
),
"description" => $description,
"url" =>
$this->fuckhtml
->getTextContent(
$a["attributes"]["href"]
),
"date" => $date,
"type" => "web",
"thumb" => [
"url" => null,
"ratio" => null
],
"sublink" => [],
"table" => []
];
}
return $out;
}
private function parse_token($response, $cookie = false){
$this->fuckhtml->load($response["data"]);
$scripts =
$this->fuckhtml
->getElementsByTagName("script");
$found = false;
foreach($scripts as $script){
preg_match(
'/window\.location ?= ?\'\/search\?([^=]+).*&n=([0-9]+)/',
$script["innerHTML"],
$tokens
);
if(isset($tokens[1])){
$found = true;
break;
}
}
if($found === false){
return false;
}
$tokens = [
$tokens[1],
$tokens[2]
];
if($cookie !== false){
// we already specified a cookie, so use the one we have already
$tokens[] = $cookie;
apcu_store("greppr_token", $tokens);
return $tokens;
}
if(!isset($response["headers"]["set-cookie"])){
// server didn't send a cookie
return false;
}
// get cookie
preg_match(
'/PHPSESSID=([^;]+)/',
$response["headers"]["set-cookie"],
$cookie
);
if(!isset($cookie[1])){
// server sent an unexpected cookie
return false;
}
$tokens[] = $cookie[1];
apcu_store("greppr_token", $tokens);
return $tokens;
}
private function limitstrlen($text){
return explode("\n", wordwrap($text, 300, "\n"))[0];
}
}
+25 -1
View File
@@ -182,6 +182,23 @@ class imgur{
throw new Exception("Failed to fetch HTML"); throw new Exception("Failed to fetch HTML");
} }
$json = json_decode($html, true);
if($json){
// {"data":{"error":"Imgur is temporarily over capacity. Please try again later."},"success":false,"status":403}
if(isset($json["data"]["error"])){
if(stripos($json["data"]["error"], "capacity")){
throw new Exception("Imgur IP blocked this 4get instance or request proxy. Try again");
}
}
throw new Exception("Imgur returned an unknown error (IP ban?)");
}
$this->fuckhtml->load($html); $this->fuckhtml->load($html);
$posts = $posts =
@@ -197,7 +214,14 @@ class imgur{
$image = $image =
$this->fuckhtml $this->fuckhtml
->getElementsByTagName("img")[0]; ->getElementsByTagName("img");
if(count($image) === 0){
continue;
}
$image = $image[0];
$image_url = "https:" . substr($this->fuckhtml->getTextContent($image["attributes"]["src"]), 0, -5); $image_url = "https:" . substr($this->fuckhtml->getTextContent($image["attributes"]["src"]), 0, -5);
+305 -10
View File
@@ -3,7 +3,10 @@
class marginalia{ class marginalia{
public function __construct(){ public function __construct(){
include "lib/fuckhtml.php"; include "lib/anubis.php";
$this->anubis = new anubis();
include_once "lib/fuckhtml.php";
$this->fuckhtml = new fuckhtml(); $this->fuckhtml = new fuckhtml();
include "lib/backend.php"; include "lib/backend.php";
@@ -102,7 +105,40 @@ class marginalia{
); );
} }
private function get($proxy, $url, $get = []){ private function get($proxy, $url, $get = [], $get_cookies = 1){
$curlproc = curl_init();
switch($get_cookies){
case 0:
$cookies = "";
$cookies_tmp = [];
curl_setopt($curlproc, CURLOPT_HEADERFUNCTION, function($curlproc, $header) use (&$cookies_tmp){
$length = strlen($header);
$header = explode(":", $header, 2);
if(trim(strtolower($header[0])) == "set-cookie"){
$cookie_tmp = explode("=", trim($header[1]), 2);
$cookies_tmp[trim($cookie_tmp[0])] =
explode(";", $cookie_tmp[1], 2)[0];
}
return $length;
});
break;
case 1:
$cookies = "";
break;
default:
$cookies = "Cookie: " . $get_cookies;
}
$headers = [ $headers = [
"User-Agent: " . config::USER_AGENT, "User-Agent: " . config::USER_AGENT,
@@ -110,6 +146,7 @@ class marginalia{
"Accept-Language: en-US,en;q=0.5", "Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip", "Accept-Encoding: gzip",
"DNT: 1", "DNT: 1",
$cookies,
"Connection: keep-alive", "Connection: keep-alive",
"Upgrade-Insecure-Requests: 1", "Upgrade-Insecure-Requests: 1",
"Sec-Fetch-Dest: document", "Sec-Fetch-Dest: document",
@@ -118,8 +155,6 @@ class marginalia{
"Sec-Fetch-User: ?1" "Sec-Fetch-User: ?1"
]; ];
$curlproc = curl_init();
if($get !== []){ if($get !== []){
$get = http_build_query($get); $get = http_build_query($get);
$url .= "?" . $get; $url .= "?" . $get;
@@ -145,11 +180,23 @@ class marginalia{
throw new Exception(curl_error($curlproc)); throw new Exception(curl_error($curlproc));
} }
if($get_cookies === 0){
$cookie = [];
foreach($cookies_tmp as $key => $value){
$cookie[] = $key . "=" . $value;
}
curl_close($curlproc); curl_close($curlproc);
return implode(";", $cookie);
}
return $data; return $data;
} }
public function web($get){ public function web($get, $ss = ""){
$search = [$get["s"]]; $search = [$get["s"]];
if(strlen($get["s"]) === 0){ if(strlen($get["s"]) === 0){
@@ -220,13 +267,14 @@ class marginalia{
"related" => [] "related" => []
]; ];
// API scraper
if(config::MARGINALIA_API_KEY !== null){ if(config::MARGINALIA_API_KEY !== null){
try{ try{
$json = $json =
$this->get( $this->get(
$this->backend->get_ip(), // no nextpage $this->backend->get_ip(), // no nextpage
"https://api.marginalia.nu/" . config::MARGINALIA_API_KEY . "/search/" . urlencode($search), "https://api.marginalia-search.com/" . config::MARGINALIA_API_KEY . "/search/" . urlencode($search),
[ [
"count" => 20 "count" => 20
] ]
@@ -241,8 +289,18 @@ class marginalia{
throw new Exception("The API key used is rate limited. Please try again in a few minutes."); throw new Exception("The API key used is rate limited. Please try again in a few minutes.");
} }
if(strpos($json, "<title>Bad Gateway (502)</title>")){
throw new Exception("Marginalia returned a 502 error");
}
$json = json_decode($json, true); $json = json_decode($json, true);
if($json === null){
throw new Exception("Failed to decode JSON");
}
foreach($json["results"] as $result){ foreach($json["results"] as $result){
$out["web"][] = [ $out["web"][] = [
@@ -263,7 +321,88 @@ class marginalia{
return $out; return $out;
} }
// no more cloudflare!! Parse html by default // HTML parser
$proxy = $this->backend->get_ip();
//
// Bypass anubis check
//
/*
if(($anubis_key = apcu_fetch("marginalia_cookie")) === false){
try{
$html =
$this->get(
$proxy,
"https://old-search.marginalia.nu/search",
[
"query" => $search
]
);
}catch(Exception $error){
throw new Exception("Failed to get anubis challenge");
}
try{
$anubis_data = $this->anubis->scrape($html);
}catch(Exception $error){
throw new Exception($error);
}
// send anubis response & get cookies
// https://old-search.marginalia.nu/.within.website/x/cmd/anubis/api/pass-challenge?response=0000018966b086834f738bacba6031028adb5aa875974ead197a8b75778baf3a&nonce=39947&redir=https%3A%2F%2Fold-search.marginalia.nu%2F&elapsedTime=1164
try{
$anubis_key =
$this->get(
$proxy,
"https://old-search.marginalia.nu/.within.website/x/cmd/anubis/api/pass-challenge",
[
"response" => $anubis_data["response"],
"nonce" => $anubis_data["nonce"],
"redir" => "https://old-search.marginalia.nu/",
"elapsedTime" => random_int(1000, 2000)
],
0
);
}catch(Exception $error){
throw new Exception("Failed to submit anubis challenge");
}
apcu_store("marginalia_cookie", $anubis_key);
}*/
if($get["npt"]){
[$params, $proxy] =
$this->backend->get(
$get["npt"],
"web"
);
try{
$html =
$this->get(
$proxy,
"https://old-search.marginalia.nu/search?" . $params,
[],
//$anubis_key
);
}catch(Exception $error){
throw new Exception("Failed to get HTML");
}
}else{
if($ss == ""){
$params = [ $params = [
"query" => $search "query" => $search
]; ];
@@ -280,21 +419,118 @@ class marginalia{
} }
} }
} }
}else{
$params = [];
}
try{ try{
$html = $html =
$this->get( $this->get(
$this->backend->get_ip(), $proxy,
"https://search.marginalia.nu/search", "https://old-search.marginalia.nu/search" . $ss,
$params $params,
//$anubis_key
); );
}catch(Exception $error){ }catch(Exception $error){
throw new Exception("Failed to get HTML"); throw new Exception("Failed to get HTML");
} }
}
$this->fuckhtml->load($html); $this->fuckhtml->load($html);
// detect meta redirect
$title =
$this->fuckhtml
->getElementsByTagName(
"title"
);
if(
count($title) !== 0 &&
$this->fuckhtml
->getTextContent(
$title[0]
) == "Error"
){
// redirect detected
// get timeout
$timeout =
$this->fuckhtml
->getElementById(
"countdown",
"b"
);
if(count($timeout) === null){
throw new Exception("Failed to find timeout value");
}
$timeout =
$this->fuckhtml
->getTextContent(
$timeout
);
preg_match(
'/location\.replace\(\'([^\']+)\'\)/',
$html,
$redirect
);
if(!isset($redirect[1])){
throw new Exception("Failed to grep redirect value");
}
$one = 1;
$redirect =
str_replace(
"/search",
"",
$redirect[1],
$one
);
sleep((int)$timeout);
return $this->web($get, $redirect);
}
// detect internal error
$infobox =
$this->fuckhtml
->getElementsByClassName("infobox");
if(count($infobox) !== 0){
foreach($infobox as $i){
$this->fuckhtml->load($i);
$h2 =
$this->fuckhtml
->getElementsByTagName("h2");
if(
count($h2) !== 0 &&
$this->fuckhtml
->getTextContent(
$h2[0]
) == "Internal error"
){
throw new Exception("Marginalia returned an internal server error");
}
}
// reset
$this->fuckhtml->load($html);
}
$sections = $sections =
$this->fuckhtml $this->fuckhtml
->getElementsByClassName( ->getElementsByClassName(
@@ -387,6 +623,65 @@ class marginalia{
]; ];
} }
// get next page
$this->fuckhtml->load($html);
$pagination =
$this->fuckhtml
->getElementsByAttributeValue(
"aria-label",
"pagination",
"nav"
);
if(count($pagination) === 0){
// no pagination
return $out;
}
$this->fuckhtml->load($pagination[0]);
$pages =
$this->fuckhtml
->getElementsByClassName(
"page-link",
"a"
);
$found_current_page = false;
foreach($pages as $page){
if(
stripos(
$page["attributes"]["class"],
"active"
) !== false
){
$found_current_page = true;
continue;
}
if($found_current_page){
// we found current page index, and we iterated over
// the next page <a>
$out["npt"] =
$this->backend->store(
parse_url(
$page["attributes"]["href"],
PHP_URL_QUERY
),
"web",
$proxy
);
break;
}
}
return $out; return $out;
} }
} }
+229 -48
View File
@@ -16,18 +16,7 @@ class mojeek{
case "web": case "web":
return [ return [
"focus" => [ "lang" => [ // lb
"display" => "Focus",
"option" => [
"any" => "No focus",
"blogs" => "Blogs",
"Dictionary" => "Dictionary",
"Recipes" => "Recipes",
"Time" => "Time",
"Weather" => "Weather"
]
],
"lang" => [
"display" => "Language", "display" => "Language",
"option" => [ "option" => [
"any" => "Any language", "any" => "Any language",
@@ -86,7 +75,7 @@ class mojeek{
"zu" => "Zulu" "zu" => "Zulu"
] ]
], ],
"country" => [ "country" => [ // rb
"display" => "Country", "display" => "Country",
"option" => [ "option" => [
"any" => "No location bias", "any" => "No location bias",
@@ -341,27 +330,20 @@ class mojeek{
"zw" => "Zimbabwe" "zw" => "Zimbabwe"
] ]
], ],
"region" => [ "sort" => [
"display" => "Region", "display" => "Sort by",
"option" => [ "option" => [
"any" => "Any region", "relevance" => "Relevance",
"eu" => "European Union", "date" => "Date" // datewr=100
"de" => "Germany",
"fr" => "France",
"uk" => "United Kingdom"
] ]
], ],
"domain" => [ "newer" => [
"display" => "Results per domain", "display" => "Newer than",
"option" => [ "option" => "_DATE"
"1" => "1 result", ],
"2" => "2 results", "older" => [
"3" => "3 results", "display" => "Older than",
"4" => "4 results", "option" => "_DATE"
"5" => "5 results",
"10" => "10 results",
"0" => "Unlimited",
]
] ]
]; ];
break; break;
@@ -371,12 +353,19 @@ class mojeek{
} }
} }
private function get($proxy, $url, $get = []){ private function get($proxy, $url, $get = [], $is_api = false){
if($is_api){
$headers = [
"Accept: application/json"
];
}else{
$headers = [ $headers = [
"User-Agent: " . config::USER_AGENT, "User-Agent: " . config::USER_AGENT,
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Language: en-US,en;q=0.5", "Accept-Language: en-US,en;q=0.9",
"Accept-Encoding: gzip", "Accept-Encoding: gzip",
"DNT: 1", "DNT: 1",
"Connection: keep-alive", "Connection: keep-alive",
@@ -386,6 +375,7 @@ class mojeek{
"Sec-Fetch-Site: none", "Sec-Fetch-Site: none",
"Sec-Fetch-User: ?1" "Sec-Fetch-User: ?1"
]; ];
}
$curlproc = curl_init(); $curlproc = curl_init();
@@ -420,6 +410,154 @@ class mojeek{
public function web($get){ public function web($get){
return $this->web_api($get);
}
public function web_api($get){
$key = $this->backend->get_key();
$proxy = $this->backend->get_ip($key["increment"]);
if($get["npt"]){
[$params, $proxy] = $this->backend->get($get["npt"], "web");
$params = json_decode($params, true);
$params["api_key"] = $key["key"];
}else{
if(strlen($get["s"]) === 0){
throw new Exception("Search term is empty!");
}
$params = [
"api_key" => $key["key"],
"q" => $get["s"],
"date" => 1,
"tlen" => 127, // title length
"dlen" => 511, // description length
"fmt" => "json"
];
if($get["lang"] != "any"){
$params["lb"] = $get["lang"];
}
if($get["country"] != "any"){
$params["rb"] = $get["country"];
}
if($get["sort"] == "date"){
$params["datewr"] = 100;
}
if($get["newer"] !== false){
$params["since"] = date("Ymd", $get["newer"]);
}
if($get["older"] !== false){
$params["before"] = date("Ymd", $get["before"]);
}
}
try{
$json =
$this->get(
$proxy,
"https://api.mojeek.com/search",
$params
);
}catch(Exception $error){
throw new Exception("Failed to get JSON");
}
$json = json_decode($json, true);
if($json === null){
throw new Exception("Failed to decode JSON");
}
$out = [
"status" => "ok",
"spelling" => [
"type" => "no_correction",
"using" => null,
"correction" => null
],
"npt" => null,
"answer" => [],
"web" => [],
"image" => [],
"video" => [],
"news" => [],
"related" => []
];
if(!isset($json["response"]["status"])){
throw new Exception("API did not return a status field");
}
if($json["response"]["status"] != "OK"){
throw new Exception("Mojeek API returned an error: " . $json["response"]["status"]);
}
if(!isset($json["response"]["results"])){
throw new Exception("Mojeek API did not return a results object");
}
foreach($json["response"]["results"] as $entry){
$out["web"][] = [
"title" => html_entity_decode($entry["title"]),
"description" => html_entity_decode($entry["desc"]),
"url" => $entry["url"],
"date" => $entry["timestamp"],
"type" => "web",
"thumb" => [
"url" => null,
"ratio" => null
],
"sublink" => [],
"table" => []
];
}
// get npt
if($json["response"]["head"]["results"] > $json["response"]["head"]["start"] + $json["response"]["head"]["return"]){
unset($params["api_key"]);
if(!isset($params["s"])){
$params["s"] = 11;
}else{
$params["s"] = $params["s"] + 10;
}
$out["npt"] =
$this->backend->store(
json_encode($params),
"web",
$proxy
);
}
return $out;
}
public function web_browser($get){
if($get["npt"]){ if($get["npt"]){
[$token, $proxy] = $this->backend->get($get["npt"], "web"); [$token, $proxy] = $this->backend->get($get["npt"], "web");
@@ -447,7 +585,7 @@ class mojeek{
$proxy = $this->backend->get_ip(); $proxy = $this->backend->get_ip();
$lang = $get["lang"]; $lang = $get["lang"];
$country = $get["country"]; $country = $get["country"];
$region = $get["region"]; //$region = $get["region"];
$domain = $get["domain"]; $domain = $get["domain"];
$focus = $get["focus"]; $focus = $get["focus"];
@@ -457,8 +595,7 @@ class mojeek{
"tn" => 7, // number of news results/page "tn" => 7, // number of news results/page
"date" => 1, // show date "date" => 1, // show date
"tlen" => 128, // max length of title "tlen" => 128, // max length of title
"dlen" => 511, // max length of description //"arc" => ($country == "any" ? "none" : $country) // location. don't use autodetect!
"arc" => ($country == "any" ? "none" : $country) // location. don't use autodetect!
]; ];
switch($focus){ switch($focus){
@@ -479,11 +616,11 @@ class mojeek{
$params["lb"] = $lang; $params["lb"] = $lang;
} }
/*
if($region != "any"){ if($region != "any"){
$params["reg"] = $region; $params["reg"] = $region;
} }*/
if($domain != "1"){ if($domain != "1"){
@@ -501,13 +638,15 @@ class mojeek{
throw new Exception("Failed to get HTML"); throw new Exception("Failed to get HTML");
} }
/*
$handle = fopen("scraper/mojeek.html", "r");
$html = fread($handle, filesize("scraper/mojeek.html"));
fclose($handle);*/
} }
if(strlen($html) === 0){
throw new Exception("Mojeek returned an empty page (probably a homepage redirect)");
}
$this->fuckhtml->load($html);
$out = [ $out = [
"status" => "ok", "status" => "ok",
"spelling" => [ "spelling" => [
@@ -524,7 +663,7 @@ class mojeek{
"related" => [] "related" => []
]; ];
$this->fuckhtml->load($html); $this->detect_block();
$results = $results =
$this->fuckhtml $this->fuckhtml
@@ -695,15 +834,18 @@ class mojeek{
preg_match( preg_match(
'/\/image\?img=([^&]+)/i', '/\/image\?img=([^&]+)/i',
$thumb[0]["attributes"]["src"], $thumb[0]["attributes"]["src"],
$thumb $matches
); );
if(count($thumb) === 2){ if(count($matches) === 2){
// for some reason, if we dont get the image from mojeek
// it sometimes fail to fetch the right image URL
$answer["thumb"] = $answer["thumb"] =
"https://mojeek.com" .
$this->fuckhtml $this->fuckhtml
->getTextContent( ->getTextContent(
$thumb[1] $thumb[0]["attributes"]["src"]
); );
} }
} }
@@ -995,7 +1137,12 @@ class mojeek{
return $out; return $out;
} }
public function news($get){ public function news(){
throw new Exception("Currently unsupported");
}
public function news_browser($get){
$search = $get["s"]; $search = $get["s"];
@@ -1032,6 +1179,8 @@ class mojeek{
$this->fuckhtml->load($html); $this->fuckhtml->load($html);
$this->detect_block();
$articles = $articles =
$this->fuckhtml->getElementsByTagName("article"); $this->fuckhtml->getElementsByTagName("article");
@@ -1164,6 +1313,38 @@ class mojeek{
return $out; return $out;
} }
private function detect_block(){
$captcha =
$this->fuckhtml
->getElementsByClassName(
"captcha-box",
"div"
);
if(count($captcha) !== 0){
throw new Exception("Mojeek returned a captcha");
}
$title =
$this->fuckhtml
->getElementsByTagName(
"title"
);
if(
count($title) !== 0 &&
$this->fuckhtml
->getTextContent(
$title[0]["innerHTML"]
) == "403 - Forbidden"
){
throw new Exception("Mojeek blocked this instance or request proxy.");
}
}
private function titledots($title){ private function titledots($title){
return trim($title, ". \t\n\r\0\x0B"); return trim($title, ". \t\n\r\0\x0B");
+1158
View File
File diff suppressed because it is too large. Load diff
+285
View File
@@ -0,0 +1,285 @@
<?php
class pexels{
public function __construct(){
include "lib/backend.php";
$this->backend = new backend("pexels");
}
public function getfilters($page){
return [
"time" => [ // date_from
"display" => "Time posted",
"option" => [
"any" => "Any time",
"last_24_hours" => "Last 24 hours",
"last_week" => "Last week",
"last_month" => "Last month",
"last_year" => "Last year"
]
],
"orientation" => [
"display" => "Orientation",
"option" => [
"any" => "Any orientation",
"landscape" => "Horizontal",
"portrait" => "Vertical",
"square" => "Square"
]
],
"color" => [
"display" => "Color",
"option" => [
"any" => "Any color",
"795548" => "Brown",
"F44336" => "Red",
"E91E63" => "Hot pink",
"9C27B0" => "Magenta",
"673AB7" => "Purple",
"3F51B5" => "Indigo",
"2196F3" => "Blue",
"03A9F4" => "Light blue",
"00BCD4" => "Cyan",
"009688" => "Forest green",
"4CAF50" => "Green",
"8BC34A" => "Lime",
"CDDC39" => "Pear",
"FFEB3B" => "Yellow",
"FFC107" => "Gold",
"FF9800" => "Orange",
"FF5722" => "Tomato",
"9E9E9E" => "Gray",
"607D8B" => "Teal",
"000000" => "Black",
"FFFFFF" => "White"
]
],
"people_count" => [
"display" => "Head count",
"option" => [
"any" => "Any number",
"0" => "0",
"1" => "1",
"2" => "2",
"3_plus" => "3+",
]
],
"people_age" => [
"display" => "People's age",
"option" => [
"any" => "Any age",
"baby" => "Baby",
"child" => "Child",
"teenager" => "Teenager",
"adult" => "Adult",
"senior_adult" => "Senior adult"
]
]
];
}
private function get($proxy, $url, $get = []){
$curlproc = curl_init();
$search = $get["query"];
if($get !== []){
$get = http_build_query($get);
$url .= "?" . $get;
}
curl_setopt($curlproc, CURLOPT_URL, $url);
curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
curl_setopt($curlproc, CURLOPT_HTTPHEADER,
["User-Agent: " . config::USER_AGENT,
"Accept: */*",
"Accept-Language: en-US,en;q=0.9",
"Accept-Encoding: gzip, deflate, br, zstd",
"Referer: https://www.pexels.com/search/" . rawurlencode($search) . "/",
"Content-Type: application/json",
"secret-key: H2jk9uKnhRmL6WPwh89zBezWvr", // hardcoded but like, people on github have been using this shit since 23'
"X-Client-Type: react",
"X-Next-Forwarded-CF-Connecting-IP: ",
"X-Next-Forwarded-CF-IPCountry: ",
"X-Next-Forwarded-CF-IPRegionCode: ",
"X-Active-Experiment: ",
"DNT: 1",
"Sec-GPC: 1",
"Alt-Used: www.pexels.com",
"Connection: keep-alive",
"Sec-Fetch-Dest: empty",
"Sec-Fetch-Mode: cors",
"Sec-Fetch-Site: same-origin",
"Priority: u=4",
"TE: trailers"]
);
curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
$this->backend->assign_proxy($curlproc, $proxy);
$data = curl_exec($curlproc);
if(curl_errno($curlproc)){
throw new Exception(curl_error($curlproc));
}
curl_close($curlproc);
return $data;
}
public function image($get){
if($get["npt"]){
[$filter, $proxy] =
$this->backend->get(
$get["npt"],
"images"
);
$filter = json_decode($filter, true);
}else{
$search = $get["s"];
if(strlen($search) === 0){
throw new Exception("Search term is empty!");
}
$proxy = $this->backend->get_ip();
// ?query=blue%20footed%20booby&page=1&per_page=24&seo_tags=true
$filter = [
"query" => $search,
"page" => 1,
"per_page" => 24,
"seo_tags" => "true"
];
// add filters
if($get["time"] != "any"){
$filter["date_from"] = $get["time"];
}
if($get["orientation"] != "any"){
$filter["orientation"] = $get["orientation"];
}
if($get["color"] != "any"){
$filter["color"] = $get["color"];
}
if($get["people_count"] != "any"){
$filter["people_count"] = $get["people_count"];
}
if($get["people_age"] != "any"){
$filter["people_age"] = $get["people_age"];
}
}
$out = [
"status" => "ok",
"npt" => null,
"image" => []
];
try{
$html =
$this->get(
$proxy,
"https://www.pexels.com/en-us/api/v3/search/photos",
$filter
);
}catch(Exception $error){
throw new Exception("Failed to fetch JSON");
}
$json = json_decode($html, true);
if(!isset($json["data"])){
throw new Exception("Pexels did not return a data object");
}
foreach($json["data"] as $image){
$thumb_size =
$this->imgratio(
(int)$image["attributes"]["width"],
(int)$image["attributes"]["height"],
350
);
$out["image"][] = [
"title" => $image["attributes"]["title"] . ": " . $image["attributes"]["description"],
"source" => [
[
"url" => $image["attributes"]["image"]["download_link"],
"width" => (int)$image["attributes"]["width"],
"height" => (int)$image["attributes"]["height"]
],
[
"url" =>
preg_replace(
'/(?:w|h)=[0-9]+$/',
"w=350",
$image["attributes"]["image"]["small"]
),
"width" => $thumb_size[0],
"height" => $thumb_size[1]
]
],
"url" =>
"https://pexels.com/photo/" .
$image["attributes"]["slug"] . "-" .
$image["attributes"]["id"] . "/"
];
}
// get next page
if((int)$json["pagination"]["current_page"] < (int)$json["pagination"]["total_pages"]){
$filter["page"]++;
$out["npt"] =
$this->backend->store(
json_encode($filter),
"images",
$proxy
);
}
return $out;
}
private function imgratio($width, $height, $max_width){
$ratio = $max_width / $width;
$new_height = floor($height * $ratio);
return [
$max_width,
$new_height
];
}
}
+268 -68
View File
@@ -13,31 +13,104 @@ class pinterest{
return []; return [];
} }
private function get($proxy, $url, $get = []){ private function get($proxy, $url, $get = [], &$cookies, $header_data_post = null){
$curlproc = curl_init(); $curlproc = curl_init();
if($header_data_post === null){
// handling GET
// extract cookies
$cookies_tmp = [];
curl_setopt($curlproc, CURLOPT_HEADERFUNCTION, function($curlproc, $header) use (&$cookies_tmp){
$length = strlen($header);
$header = explode(":", $header, 2);
if(trim(strtolower($header[0])) == "set-cookie"){
$cookie_tmp = explode("=", trim($header[1]), 2);
$cookies_tmp[trim($cookie_tmp[0])] =
explode(";", $cookie_tmp[1], 2)[0];
}
return $length;
});
curl_setopt($curlproc, CURLOPT_HTTPHEADER,
["User-Agent: " . config::USER_AGENT,
"Accept: application/json, text/javascript, */*, q=0.01",
"Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip",
"Referer: https://ca.pinterest.com/",
"X-Requested-With: XMLHttpRequest",
"X-APP-VERSION: 78f8764",
"X-Pinterest-AppState: active",
"X-Pinterest-Source-Url: /",
"X-Pinterest-PWS-Handler: www/index.js",
"screen-dpr: 1",
"is-preload-enabled: 1",
"DNT: 1",
"Sec-GPC: 1",
"Sec-Fetch-Dest: empty",
"Sec-Fetch-Mode: cors",
"Sec-Fetch-Site: same-origin",
"Connection: keep-alive",
"Alt-Used: ca.pinterest.com",
"Priority: u=0",
"TE: trailers"]
);
if($get !== []){ if($get !== []){
$get = http_build_query($get); $get = http_build_query($get);
$url .= "?" . $get; $url .= "?" . $get;
} }
}else{
// handling POST (pagination)
$get = http_build_query($get);
curl_setopt($curlproc, CURLOPT_HTTPHEADER,
["User-Agent: " . config::USER_AGENT,
"Accept: application/json, text/javascript, */*, q=0.01",
"Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip",
"Content-Type: application/x-www-form-urlencoded",
"Content-Length: " . strlen($get),
"Referer: https://ca.pinterest.com/",
"X-Requested-With: XMLHttpRequest",
"X-APP-VERSION: 78f8764",
"X-CSRFToken: " . $cookies["csrf"],
"X-Pinterest-AppState: active",
"X-Pinterest-Source-Url: /search/pins/?rs=ac&len=2&q=" . urlencode($header_data_post) . "&eq=" . urlencode($header_data_post),
"X-Pinterest-PWS-Handler: www/search/[scope].js",
"screen-dpr: 1",
"is-preload-enabled: 1",
"Origin: https://ca.pinterest.com",
"DNT: 1",
"Sec-GPC: 1",
"Sec-Fetch-Dest: empty",
"Sec-Fetch-Mode: cors",
"Sec-Fetch-Site: same-origin",
"Connection: keep-alive",
"Alt-Used: ca.pinterest.com",
"Cookie: " . $cookies["cookie"],
"TE: trailers"]
);
curl_setopt($curlproc, CURLOPT_POST, true);
curl_setopt($curlproc, CURLOPT_POSTFIELDS, $get);
}
curl_setopt($curlproc, CURLOPT_URL, $url); curl_setopt($curlproc, CURLOPT_URL, $url);
curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
curl_setopt($curlproc, CURLOPT_HTTPHEADER,
["User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/110.0", // http2 bypass
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", curl_setopt($curlproc, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
"Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip",
"DNT: 1",
"Connection: keep-alive",
"Upgrade-Insecure-Requests: 1",
"Sec-Fetch-Dest: document",
"Sec-Fetch-Mode: navigate",
"Sec-Fetch-Site: none",
"Sec-Fetch-User: ?1"]
);
curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true); curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2); curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
@@ -54,6 +127,26 @@ class pinterest{
throw new Exception(curl_error($curlproc)); throw new Exception(curl_error($curlproc));
} }
if($header_data_post === null){
if(!isset($cookies_tmp["csrftoken"])){
throw new Exception("Failed to grep CSRF token");
}
$cookies = "";
foreach($cookies_tmp as $cookie_name => $cookie_value){
$cookies .= $cookie_name . "=" . $cookie_value . "; ";
}
$cookies = [
"csrf" => $cookies_tmp["csrftoken"],
"cookie" => rtrim($cookies, " ;")
];
}
curl_close($curlproc); curl_close($curlproc);
return $data; return $data;
} }
@@ -62,17 +155,68 @@ class pinterest{
if($get["npt"]){ if($get["npt"]){
// @TODO [$data, $proxy] =
// post data for next page $this->backend->get(
$data = [ $get["npt"], "images"
"source_url" => "/search/pins/?q=" . urlencode($search) . "&rs=typed",
"data" =>
json_encode(
[
// {"options":{"applied_filters":null,"appliedProductFilters":"---","article":null,"auto_correction_disabled":false,"corpus":null,"customized_rerank_type":null,"domains":null,"filters":null,"journey_depth":null,"page_size":null,"price_max":null,"price_min":null,"query_pin_sigs":null,"query":"higurashi","redux_normalize_feed":true,"rs":"typed","scope":"pins","selected_one_bar_modules":null,"source_id":null,"source_module_id":null,"top_pin_id":null,"bookmarks":["Y2JVSG81V2sxcmNHRlpWM1J5VFVad1ZsWlVRbXhpVmtreVZsZHpOV0pIU2tkV2FscFhVbXhhVkZreU1WSmtNREZWVjIxR1RrMXNTbEJXYlhSaFVtMVdjMVZ1U2xaaWEzQnpXVlJPVTJWV1pISlhhM1JYVm10V05sVldVbE5XVjBwMVVXMUdWVll6VFhoVWJYaFhWMVp3Ums1V1RsTmlSbGt5Vm10YWFtVkdWbkpOU0dSUFZsZG9XRmxzWkc5VlZscHlWbGhrYkdKR1NubFdWelZQWVVaYWRHVkVRbFppUmtwVVZrUktWMlJIVWtWV2JHaHBVakZLU0Zkc1pEUmtNVnBZVW10b2FsSXdXbkJXYlRWRFpHeGFSMWRzVG1oaGVrWllXV3RvVTFVeFpFaFZiRUpoVm5wRk1GbHFSbXRYVjA1R1YyczFWMVpHV2pSWFZtaDNVakZrY2sxWVRsaGlhM0JXV1ZSR1MyRkdiRlZTYm1SVVVteHdXbGxWVlRGVk1VbDVWRmhrVjAxdVVuWlVhMXBTWlVaT2MxcEhSbE5TTWswMVdtdGFWMU5YU2paVmJYaFRUVmhDUjFZeU5YZFVNVkY0VjJ0b1ZXRnJOVlpVVmxwTFVURndXR042VmxOV2ExcGFXVlZWTlZVeFNYZE5WRTVYVWtWYVZGWkhNVTlXTVU1WllVWk9hR1ZyV2s1WFZ6QXhZakpPVjFWWWFHRlNWbkJRVm14U1IwMUdXWGxOVkVKVlRWWnNORll5TURWV1YwVjVWV3hDV21FeGNETmFSVnByVjFkS1IyTkhhR2xYUjJkM1ZtdGFhMlF4VVhsVGJGcE9Wa1p3YjFwWGVFdFZWbFp4VW14YWJGWnRVbHBaTUdoTFZHMUtTR1ZJYUZkV2VrWjJWMVphU21ReVJYcGpSbFpwVW10d1RGZHJVa0pPVms1SFZHNVNUbFl3V2xoVmJYUldaVVpaZUZremFGUk5hM0JYVkZaYVYyRkZNSGxWYkVKYVlrWlZlRnBGV210WFIwNUpVMnMxVTFaR1dscFdWekI0VFVaV1IxTllaR3BUUlhCb1dWUkdWbVZHVm5SbFJuQnNZbFpKTWxSVlVYaFBSVGxGV1hwR1QyVnJSVEZVVlZKT1RrVXhSVkpVUWs5bGJFVXhWRmhzZDFOR1ZsWmtNMFp0VWpGYWIxZFhjRXBsUlRGSVZWaHdUbFl4YTNoVVZWSnFUVVUxV0ZadGFFOVNSVnB6Vkd0a1drMUdiRFpUVkVaT1pXMWplRmRzVWxkaFJuQllWVlJTVDJWdFRqWlVNVkpTWlZad2NWcEhkRTlsYTFwMFZGVlNhMkpWTVZWVFZFcE9Wa1pzTmxkWE1WSk9WVEYwVlcweFVGWXdXVFJXUjNSWFYwZGFRbEJVTVRoUFJHTXhUbnBCTlUxRVRUUk5SRVV3VG5wUk5VMTVjRWhWVlhkeFprUlZlRTlFVVRKWlZHc3lUMWRSTWsxVVVUSk9iVnBvV1RKWmVrNTZXWGhPTWs1cFQwUkZNVTlFVm1sTlZGcHBUV3BTYTFsWFRtcE9SR015VG1wVk5GbHFaR2haVjFacldWUmFiVmxxWkdoYVZGWnFUa1JXT0ZSclZsaG1RVDA5fFVIbzVhRkpYZUc1WFYyUlpWVEpHYkdGNk1XWk5ha1ptVFZSR09FOUVZekZPZWtFMVRVUk5ORTFFUlRCT2VsRTFUWGx3U0ZWVmQzRm1SMWw1VFZSUk1WbDZUVEJhUjFGNVQxZFNhVnB0VlRGT1JFVXdXVlJuZVU1cVRUUk5hbU40VDBSSk1VNXFWVEZOYlZwcVdsUnJlRTFFVVhwWmVsVjNXbXBvYkU1dFJYbE9ha0Y2VDFSSk5VMTZWVEJaYWtJNFZHdFdXR1pCUFQwPXxOb25lfDg3NTcwOTAzODAxNDc0OTMqR1FMKnwzMjM3YjM3ZGNhMGU3YjYyYzYzYzAyZGJkNGU1MjdlNzMyMTExMTNlMmUyMzEyOWM2MDAzYmU1ZTlmZjkwYjAwfE5FV3w="]},"context":{}}
]
); );
];
$data = json_decode($data, true);
$search = $data["q"];
$cookies = $data["cookies"];
try{
$json =
$this->get(
$proxy,
"https://ca.pinterest.com/resource/BaseSearchResource/get/",
[
"source_url" => "/search/pins/?q=" . urlencode($search) . "&rs=typed",
"data" => json_encode(
[
"options" => [
"applied_unified_filters" => null,
"appliedProductFilters" => "---",
"article" => null,
"auto_correction_disabled" => false,
"corpus" => null,
"customized_rerank_type" => null,
"domains" => null,
"dynamicPageSizeExpGroup" => null,
"filters" => null,
"journey_depth" => null,
"page_size" => null,
"price_max" => null,
"price_min" => null,
"query_pin_sigs" => null,
"query" => $data["q"],
"redux_normalize_feed" => true,
"request_params" => null,
"rs" => "typed",
"scope" => "pins",
"selected_one_bar_modules" => null,
"source_id" => null,
"source_module_id" => null,
"source_url" => "/search/pins/?q=" . urlencode($search) . "&rs=typed",
"top_pin_id" => null,
"top_pin_ids" => null,
"bookmarks" => [
$data["bookmark"]
]
],
"context" => []
],
JSON_UNESCAPED_SLASHES
)
],
$cookies,
$search
);
}catch(Exception $error){
throw new Exception("Failed to fetch JSON");
}
}else{ }else{
@@ -82,26 +226,44 @@ class pinterest{
throw new Exception("Search term is empty!"); throw new Exception("Search term is empty!");
} }
// https://ca.pinterest.com/resource/BaseSearchResource/get/?source_url=%2Fsearch%2Fpins%2F%3Feq%3Dhigurashi%26etslf%3D5966%26len%3D2%26q%3Dhigurashi%2520when%2520they%2520cry%26rs%3Dac&data=%7B%22options%22%3A%7B%22applied_unified_filters%22%3Anull%2C%22appliedProductFilters%22%3A%22---%22%2C%22article%22%3Anull%2C%22auto_correction_disabled%22%3Afalse%2C%22corpus%22%3Anull%2C%22customized_rerank_type%22%3Anull%2C%22domains%22%3Anull%2C%22dynamicPageSizeExpGroup%22%3Anull%2C%22filters%22%3Anull%2C%22journey_depth%22%3Anull%2C%22page_size%22%3Anull%2C%22price_max%22%3Anull%2C%22price_min%22%3Anull%2C%22query_pin_sigs%22%3Anull%2C%22query%22%3A%22higurashi%20when%20they%20cry%22%2C%22redux_normalize_feed%22%3Atrue%2C%22request_params%22%3Anull%2C%22rs%22%3A%22ac%22%2C%22scope%22%3A%22pins%22%2C%22selected_one_bar_modules%22%3Anull%2C%22source_id%22%3Anull%2C%22source_module_id%22%3Anull%2C%22source_url%22%3A%22%2Fsearch%2Fpins%2F%3Feq%3Dhigurashi%26etslf%3D5966%26len%3D2%26q%3Dhigurashi%2520when%2520they%2520cry%26rs%3Dac%22%2C%22top_pin_id%22%3Anull%2C%22top_pin_ids%22%3Anull%7D%2C%22context%22%3A%7B%7D%7D&_=1736116313987
// source_url=%2Fsearch%2Fpins%2F%3Feq%3Dhigurashi%26etslf%3D5966%26len%3D2%26q%3Dhigurashi%2520when%2520they%2520cry%26rs%3Dac
// &data=%7B%22options%22%3A%7B%22applied_unified_filters%22%3Anull%2C%22appliedProductFilters%22%3A%22---%22%2C%22article%22%3Anull%2C%22auto_correction_disabled%22%3Afalse%2C%22corpus%22%3Anull%2C%22customized_rerank_type%22%3Anull%2C%22domains%22%3Anull%2C%22dynamicPageSizeExpGroup%22%3Anull%2C%22filters%22%3Anull%2C%22journey_depth%22%3Anull%2C%22page_size%22%3Anull%2C%22price_max%22%3Anull%2C%22price_min%22%3Anull%2C%22query_pin_sigs%22%3Anull%2C%22query%22%3A%22higurashi%20when%20they%20cry%22%2C%22redux_normalize_feed%22%3Atrue%2C%22request_params%22%3Anull%2C%22rs%22%3A%22ac%22%2C%22scope%22%3A%22pins%22%2C%22selected_one_bar_modules%22%3Anull%2C%22source_id%22%3Anull%2C%22source_module_id%22%3Anull%2C%22source_url%22%3A%22%2Fsearch%2Fpins%2F%3Feq%3Dhigurashi%26etslf%3D5966%26len%3D2%26q%3Dhigurashi%2520when%2520they%2520cry%26rs%3Dac%22%2C%22top_pin_id%22%3Anull%2C%22top_pin_ids%22%3Anull%7D%2C%22context%22%3A%7B%7D%7D
// &_=1736116313987
$source_url = "/search/pins/?q=" . urlencode($search) . "&rs=" . urlencode($search);
$filter = [ $filter = [
"source_url" => "/search/pins/?q=" . urlencode($search), "source_url" => $source_url,
"rs" => "typed", "rs" => "typed",
"data" => "data" =>
json_encode( json_encode(
[ [
"options" => [ "options" => [
"article" => null, "applied_unified_filters" => null,
"applied_filters" => null,
"appliedProductFilters" => "---", "appliedProductFilters" => "---",
"auto_correction_disabled" => false, "article" => null,
"corpus" => null, "corpus" => null,
"customized_rerank_type" => null, "customized_rerank_type" => null,
"domains" => null,
"dynamicPageSizeExpGroup" => null,
"filters" => null, "filters" => null,
"query" => $search, "journey_depth" => null,
"page_size" => null,
"price_max" => null,
"price_min" => null,
"query_pin_sigs" => null, "query_pin_sigs" => null,
"query" => $search,
"redux_normalize_feed" => true, "redux_normalize_feed" => true,
"rs" => "typed", "request_params" => null,
"rs" => "ac",
"scope" => "pins", // pins, boards, videos, "scope" => "pins", // pins, boards, videos,
"source_id" => null "selected_one_bar_modules" => null,
"source_id" => null,
"source_module_id" => null,
"source_url" => $source_url,
"top_pin_id" => null,
"top_pin_ids" => null
], ],
"context" => [] "context" => []
] ]
@@ -110,35 +272,99 @@ class pinterest{
]; ];
$proxy = $this->backend->get_ip(); $proxy = $this->backend->get_ip();
} $cookies = [];
try{ try{
$json = $json =
json_decode(
$this->get( $this->get(
$proxy, $proxy,
"https://www.pinterest.ca/resource/BaseSearchResource/get/", "https://ca.pinterest.com/resource/BaseSearchResource/get/",
$filter $filter,
), $cookies,
true null
); );
}catch(Exception $error){ }catch(Exception $error){
throw new Exception("Failed to fetch JSON"); throw new Exception("Failed to fetch JSON");
} }
}
$json = json_decode($json, true);
if($json === null){ if($json === null){
throw new Exception("Failed to decode JSON"); throw new Exception("Failed to decode JSON");
} }
if(
isset($json["client_context"]["is_bad_bot"]) &&
(int)$json["client_context"]["is_bad_bot"] === 1
){
throw new Exception("Pinterest blocked this instance or request proxy.");
}
$out = [ $out = [
"status" => "ok", "status" => "ok",
"npt" => null, "npt" => null,
"image" => [] "image" => []
]; ];
if(
!isset(
$json["resource_response"]
["status"]
)
){
throw new Exception("Unknown API failure");
}
if($json["resource_response"]["status"] != "success"){
$status = "Got non-OK response: " . $json["resource_response"]["status"];
if(
isset(
$json["resource_response"]["message"]
)
){
$status .= " - " . $json["resource_response"]["message"];
}
throw new Exception($status);
}
if(
isset(
$json["resource_response"]["sensitivity"]
["notices"][0]["description"]["text"]
)
){
throw new Exception(
"Pinterest returned a notice: " .
$json["resource_response"]["sensitivity"]["notices"][0]["description"]["text"]
);
}
// get NPT
if(isset($json["resource_response"]["bookmark"])){
$out["npt"] =
$this->backend->store(
json_encode([
"q" => $search,
"bookmark" => $json["resource_response"]["bookmark"],
"cookies" => $cookies
]),
"images",
$proxy
);
}
foreach( foreach(
$json $json
["resource_response"] ["resource_response"]
@@ -150,6 +376,7 @@ class pinterest{
switch($item["type"]){ switch($item["type"]){
case "pin": case "pin":
case "board":
/* /*
Handle image object Handle image object
@@ -206,42 +433,15 @@ class pinterest{
"height" => (int)$thumb["height"] "height" => (int)$thumb["height"]
] ]
], ],
"url" => "https://www.pinterest.com/pin/" . $item["id"]
];
break;
case "board":
if(isset($item["cover_pin"]["image_url"])){
$image = [
"url" => $item["cover_pin"]["image_url"],
"width" => (int)$item["cover_pin"]["size"][0],
"height" => (int)$item["cover_pin"]["size"][1]
];
}elseif(isset($item["image_cover_url_hd"])){
/*
$image = [
"url" => "url" =>
"width" => null, !isset($item["link"]) ?
"height" => null "https://ca.pinterest.com/pin/" . $item["id"] :
];*/ $item["link"]
} ];
break; break;
} }
} }
return $out; return $out;
} }
private function getfullresimage($image, $has_og){
$has_og = $has_og ? "1200x" : "originals";
return
preg_replace(
'/https:\/\/i\.pinimg\.com\/[^\/]+\//',
"https://i.pinimg.com/" . $has_og . "/",
$image
);
}
} }
+396
View File
@@ -0,0 +1,396 @@
<?php
class pixabay{
public function __construct(){
include "lib/backend.php";
$this->backend = new backend("pixabay");
}
public function getfilters($page){
return [
"nsfw" => [
"display" => "NSFW",
"option" => [
"yes" => "Yes",
"no" => "No"
]
],
"time" => [
"display" => "Time posted",
"option" => [
"any" => "Any time",
"1d" => "< 24 hours",
"3d" => "< 72 hours",
"1w" => "< 7 days",
"6m" => "< 6 months",
"1y" => "< 12 months"
]
],
"category" => [
"display" => "Category",
"option" => [
"images" => "All images",
"photos" => "Photos",
"illustrations" => "Illustrations",
"vectors" => "Vectors",
"gifs" => "GIFs"
]
],
"content_type" => [
"display" => "Authenticity",
"option" => [
"authentic" => "Authentic only",
"show_all" => "Authentic + AI slop",
"ai" => "AI slop only"
]
],
"order" => [
"display" => "Sort by",
"option" => [
"relevance" => "Most relevant",
"latest" => "Latest",
"ec" => "Editor's choice",
"trending" => "Trending"
]
],
"orientation" => [
"display" => "Orientation",
"option" => [
"any" => "Any orientation",
"vertical" => "Vertical",
"horizontal" => "Horizontal"
]
],
"color" => [
"display" => "Color",
"option" => [
"any" => "Any color",
"transparent" => "Transparent",
"grayscale" => "Grayscale",
"red" => "Red",
"orange" => "Orange",
"yellow" => "Yellow",
"green" => "Green",
"turquoise" => "Turquoise",
"blue" => "Blue",
"brown" => "Brown",
"black" => "Black",
"gray" => "Gray",
"white" => "White",
"pink" => "Pink",
"lilac" => "Lilac"
]
]
];
}
private function get($proxy, $url, $get = [], $nsfw = true){
$curlproc = curl_init();
if($get !== []){
$get = http_build_query($get);
$url .= "?" . $get;
}
curl_setopt($curlproc, CURLOPT_URL, $url);
curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
curl_setopt($curlproc, CURLOPT_HTTPHEADER,
["User-Agent: " . config::USER_AGENT . " Pixabay",
"Accept: application/json",
"Accept-Language: en-US,en;q=0.9",
"Accept-Encoding: gzip, deflate, br, zstd",
"Referer: {$url}",
"Cookie: " . ($nsfw ? "g_rated=off" : "g_rated=1"),
"DNT: 1",
"Sec-GPC: 1",
"Connection: keep-alive",
"Sec-Fetch-Dest: empty",
"Sec-Fetch-Mode: no-cors",
"Sec-Fetch-Site: same-origin",
"x-fetch-bootstrap: 1",
"Alt-Used: pixabay.com",
"Priority: u=0",
"Pragma: no-cache",
"Cache-Control: no-cache"]
);
curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
$this->backend->assign_proxy($curlproc, $proxy);
$data = curl_exec($curlproc);
if(curl_errno($curlproc)){
throw new Exception(curl_error($curlproc));
}
curl_close($curlproc);
return $data;
}
public function image($get){
if($get["npt"]){
[$npt, $proxy] =
$this->backend->get(
$get["npt"],
"images"
);
$npt = json_decode($npt, true);
}else{
$search = $get["s"];
if(strlen($search) === 0){
throw new Exception("Search term is empty!");
}
$proxy = $this->backend->get_ip();
$npt = [
"s" => $search,
"pagi" => 1,
"category" => $get["category"]
];
// filters
if($get["content_type"] != "show_all"){
$npt["content_type"] = $get["content_type"];
}
if($get["order"] != "relevance"){
$npt["order"] = $get["order"];
}
$npt["nsfw"] = $get["nsfw"] == "yes" ? true : false;
if($get["orientation"] != "any"){
$npt["orientation"] = $get["orientation"];
}
if($get["color"] != "any"){
$npt["colors"] = $get["color"];
}
if($get["time"] != "any"){
$npt["date"] = $get["time"];
}
}
$out = [
"status" => "ok",
"npt" => null,
"image" => []
];
// https://pixabay.com/images/search/japan/?pagi=1
$npt_pass = $npt;
unset($npt_pass["s"]);
unset($npt_pass["category"]);
unset($npt_pass["nsfw"]);
try{
$json =
$this->get(
$proxy,
"https://pixabay.com/{$npt["category"]}/search/" . rawurlencode($npt["s"]) . "/",
$npt_pass,
$npt["nsfw"]
);
}catch(Exception $error){
throw new Exception("Failed to fetch JSON");
}
$json = json_decode($json, true);
if($json === null){
throw new Exception("Failed to decode JSON");
}
if(!isset($json["page"]["results"])){
throw new Exception("Pixabay API did not return a results object");
}
//print_r($json);
foreach($json["page"]["results"] as $image){
if(
!(
$image["mediaType"] == "photo" ||
$image["mediaType"] == "animation"
)
){
continue;
}
// handle images that supply canvaRetouchUrl
if(
isset($image["canvaRetouchUrl"]) &&
$image["canvaRetouchUrl"] !== null
){
parse_str(
parse_url(
$image["canvaRetouchUrl"],
PHP_URL_QUERY
),
$base
);
if(!isset($base["image-url"])){
// should not happen
continue;
}
// get extension
$base = $base["image-url"];
$parsed_base = parse_url($base);
$ext =
explode(
".",
$parsed_base["path"]
);
$ext = $ext[count($ext) - 1];
// restructure url
$base =
explode(
"_",
$base
);
unset($base[count($base) - 1]);
$base = implode("_", $base);
$ratio_1x = $this->imgratio((int)$image["width"], (int)$image["height"], 180);
$ratio_2x = $this->imgratio((int)$image["width"], (int)$image["height"], 1920);
$source = [
[
"url" => "{$base}_1920.{$ext}",
"width" => $ratio_2x[0],
"height" => $ratio_2x[1]
],
[
"url" => "{$base}_180.{$ext}",
"width" => $ratio_1x[0],
"height" => $ratio_1x[1]
]
];
}else{
// get intended sizes if canva is not set
// get 1x
$source_1x = null;
if(isset($image["sources"]["image_1x"])){ $source_1x = $image["sources"]["image_1x"]; }
elseif(isset($image["sources"]["1x"])){ $source_1x = $image["sources"]["1x"]; }
if($source_1x === null){ continue; } // should not happen
preg_match(
'/_([0-9]+)\./',
$image["sources"]["1x"],
$size_1x
);
$size_1x = (int)$size_1x[1];
// get 2x
$source_2x = null;
if(isset($image["sources"]["gif_2x"])){ $source_2x = $image["sources"]["gif_2x"]; }
elseif(isset($image["sources"]["2x"])){ $source_2x = $image["sources"]["2x"]; }
if($source_2x === null){ continue; } // should not happen
preg_match(
'/_([0-9]+)\./',
$source_2x,
$size_2x
);
$size_2x = (int)$size_2x[1];
// compute ratios
$ratio_1x = $this->imgratio((int)$image["width"], (int)$image["height"], $size_1x);
$ratio_2x = $this->imgratio((int)$image["width"], (int)$image["height"], $size_2x);
// handle images that only give normal thumbnails
$source = [
[
"url" => $source_2x,
"width" => $ratio_2x[0],
"height" => $ratio_2x[1]
],
[
"url" => $source_1x,
"width" => $ratio_1x[0],
"height" => $ratio_1x[1]
]
];
}
$ratio_1x = $this->imgratio((int)$image["width"], (int)$image["height"], 180);
$ratio_2x = $this->imgratio((int)$image["width"], (int)$image["height"], 1920);
$out["image"][] = [
"title" => $image["name"],
"source" => $source,
"url" => "https://pixabay.com" . $image["href"]
];
}
// add next page
if((int)$json["page"]["page"] < (int)$json["page"]["pages"]){
$npt["pagi"]++;
$out["npt"] =
$this->backend->store(
json_encode($npt),
"images",
$proxy
);
}
return $out;
}
private function imgratio($width, $height, $max_width){
$ratio = $max_width / $width;
$new_height = floor($height * $ratio);
return [
$max_width,
$new_height
];
}
}
+350
View File
@@ -0,0 +1,350 @@
<?php
class purili{
public function __construct(){
include "lib/backend.php";
$this->backend = new backend("purili");
}
public function getfilters($page){
switch($page){
case "web":
return [
"nsfw" => [
"display" => "NSFW",
"option" => [
"yes" => "Yes",
"maybe" => "Maybe",
"no" => "No"
]
],
"language" => [
"display" => "Language",
"option" => [
"any" => "Any language",
"en" => "English only",
"nl" => "Dutch preferred",
"de" => "German preferred",
"fr" => "French preferred",
"es" => "Spanish preferred",
"it" => "Italian preferred",
"pt" => "Portuguese preferred"
]
]
];
break;
case "videos":
return [];
break;
}
}
private function get($proxy, $url, $get = [], $nsfw = null, $language = null){
$curlproc = curl_init();
switch($nsfw){
case "yes":
case null:
$get["fv"] = "0";
$nsfw = "off";
break;
case "maybe":
$get["fv"] = "1";
$nsfw = "moderate";
break;
case "no":
$get["fv"] = "2";
$nsfw = "strict";
break;
}
switch($language){
case "any":
case null:
$language_cookie = "";
break;
default:
$language_cookie = "; purili-language={$language}";
break;
}
if($get !== []){
$get = http_build_query($get);
$url .= "?" . $get;
}
curl_setopt($curlproc, CURLOPT_URL, $url);
curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
curl_setopt($curlproc, CURLOPT_HTTPHEADER,
["User-Agent: " . config::USER_AGENT,
"Accept: */*",
"Accept-Language: en-US,en;q=0.9",
"Accept-Encoding: gzip, deflate, br, zstd",
"DNT: 1",
"Sec-GPC: 1",
"Connection: keep-alive",
"Cookie: purili-safesearch={$nsfw}{$language_cookie}",
"Sec-Fetch-Dest: empty",
"Sec-Fetch-Mode: cors",
"Sec-Fetch-Site: same-origin",
"Priority: u=4"]
);
curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
$this->backend->assign_proxy($curlproc, $proxy);
$data = curl_exec($curlproc);
if(curl_errno($curlproc)){
throw new Exception(curl_error($curlproc));
}
curl_close($curlproc);
return $data;
}
public function web($get){
if($get["npt"]){
[$q, $proxy] = $this->backend->get($get["npt"], "web");
$q = json_decode($q, true);
$nsfw = $q["nsfw"];
unset($q["nsfw"]);
$language = $q["language"];
unset($q["language"]);
}else{
$search = $get["s"];
if(strlen($search) === 0){
throw new Exception("Search term is empty!");
}
$proxy = $this->backend->get_ip();
$q = [
"q" => $search,
"page" => 1,
// added at $this->get()
// "fv" => 0
];
$nsfw = $get["nsfw"];
$language = $get["language"];
}
// https://puri.li/api/search?q=4get&page=1&fv=0
try{
$json = $this->get(
$proxy,
"https://puri.li/api/search",
$q,
$nsfw,
$language
);
}catch(Exception $error){
throw new Exception("Failed to hit API");
}
$json = json_decode($json, true);
if($json === null){
throw new Exception("Failed to decode JSON");
}
if(!isset($json["results"])){
throw new Exception("Failed to access results array");
}
$out = [
"status" => "ok",
"spelling" => [
"type" => "no_correction",
"using" => null,
"correction" => null
],
"npt" => null,
"answer" => [],
"web" => [],
"image" => [],
"video" => [],
"news" => [],
"related" => []
];
foreach($json["results"] as $result){
$out["web"][] = [
"title" => $result["title"],
"description" => $result["description"],
"url" => $result["url"],
"date" => null,
"type" => "web",
"thumb" => [
"url" => null,
"ratio" => null
],
"sublink" => [],
"table" => []
];
}
// get next page
if($json["hasNext"]){
$q["page"]++;
$q["nsfw"] = $nsfw;
$q["language"] = $language;
$out["npt"] =
$this->backend->store(
json_encode($q),
"web",
$proxy
);
}
return $out;
}
public function video($get){
if($get["npt"]){
[$q, $proxy] = $this->backend->get($get["npt"], "videos");
$q = json_decode($q, true);
}else{
$search = $get["s"];
if(strlen($search) === 0){
throw new Exception("Search term is empty!");
}
$proxy = $this->backend->get_ip();
$q = [
"q" => $search,
"offset" => 0,
"limit" => 12
// added at $this->get()
// "fv" => 0
];
}
// https://puri.li/api/videos/search?q=higurashi&limit=12&fv=1
// hidden &offset parameter only accessible through the API
try{
$json = $this->get(
$proxy,
"https://puri.li/api/videos/search",
$q,
null,
null
);
}catch(Exception $error){
throw new Exception("Failed to hit API");
}
$json = json_decode($json, true);
if($json === null){
throw new Exception("Failed to decode JSON");
}
if(!isset($json["results"])){
throw new Exception("Failed to access results array");
}
$out = [
"status" => "ok",
"npt" => null,
"video" => [],
"author" => [],
"livestream" => [],
"playlist" => [],
"reel" => []
];
foreach($json["results"] as $result){
if(isset($result["thumbnail"])){
$thumb = [
"ratio" => "16:9",
"url" => $result["thumbnail"]
];
}else{
$thumb = [
"ratio" => null,
"url" => null
];
}
$out["video"][] = [
"title" => $result["title"],
"description" =>
str_replace(
"\n", " ",
mb_substr(
$result["description"], 0, 255, "utf-8"
)
),
"author" => [
"name" => null,
"url" => isset($result["channel_id"]) ? "https://www.youtube.com/channel/" . $result["channel_id"] : null,
"avatar" => null
],
"date" => strtotime($result["upload_date"]),
"duration" => $result["duration"],
"views" => $result["view_count"],
"thumb" => $thumb,
"url" => $result["url"]
];
}
// get next page
if($json["limit"] + $json["offset"] < $json["total"]){
$q["offset"] = $q["offset"] + $json["limit"];
$out["npt"] =
$this->backend->store(
json_encode($q),
"videos",
$proxy
);
}
return $out;
}
}
-937
View File
@@ -1,937 +0,0 @@
<?php
class qwant{
public function __construct(){
include "lib/backend.php";
$this->backend = new backend("qwant");
}
public function getfilters($page){
$base = [
"nsfw" => [
"display" => "NSFW",
"option" => [
"yes" => "Yes",
"maybe" => "Maybe",
"no" => "No"
]
],
"country" => [
"display" => "Country",
"option" => [
"en_US" => "United States",
"fr_FR" => "France",
"en_GB" => "Great Britain",
"de_DE" => "Germany",
"it_IT" => "Italy",
"es_AR" => "Argentina",
"en_AU" => "Australia",
"es_ES" => "Spain (es)",
"ca_ES" => "Spain (ca)",
"cs_CZ" => "Czech Republic",
"ro_RO" => "Romania",
"el_GR" => "Greece",
"zh_CN" => "China",
"zh_HK" => "Hong Kong",
"en_NZ" => "New Zealand",
"fr_FR" => "France",
"th_TH" => "Thailand",
"ko_KR" => "South Korea",
"sv_SE" => "Sweden",
"nb_NO" => "Norway",
"da_DK" => "Denmark",
"hu_HU" => "Hungary",
"et_EE" => "Estonia",
"es_MX" => "Mexico",
"es_CL" => "Chile",
"en_CA" => "Canada (en)",
"fr_CA" => "Canada (fr)",
"en_MY" => "Malaysia",
"bg_BG" => "Bulgaria",
"fi_FI" => "Finland",
"pl_PL" => "Poland",
"nl_NL" => "Netherlands",
"pt_PT" => "Portugal",
"de_CH" => "Switzerland (de)",
"fr_CH" => "Switzerland (fr)",
"it_CH" => "Switzerland (it)",
"de_AT" => "Austria",
"fr_BE" => "Belgium (fr)",
"nl_BE" => "Belgium (nl)",
"en_IE" => "Ireland",
"he_IL" => "Israel"
]
]
];
switch($page){
case "web":
$base = array_merge(
$base,
[
"time" => [
"display" => "Time posted",
"option" => [
"any" => "Any time",
"day" => "Past 24 hours",
"week" => "Past week",
"month" => "Past month"
]
],
"extendedsearch" => [
// no display, wont show in interface
"option" => [
"yes" => "Yes",
"no" => "No"
]
]
]
);
break;
case "images":
$base = array_merge(
$base,
[
"time" => [
"display" => "Time posted",
"option" => [
"any" => "Any time",
"day" => "Past 24 hours",
"week" => "Past week",
"month" => "Past month"
]
],
"size" => [
"display" => "Size",
"option" => [
"any" => "Any size",
"large" => "Large",
"medium" => "Medium",
"small" => "Small"
]
],
"color" => [
"display" => "Color",
"option" => [
"any" => "Any color",
"coloronly" => "Color only",
"monochrome" => "Monochrome",
"black" => "Black",
"brown" => "Brown",
"gray" => "Gray",
"white" => "White",
"yellow" => "Yellow",
"orange" => "Orange",
"red" => "Red",
"pink" => "Pink",
"purple" => "Purple",
"blue" => "Blue",
"teal" => "Teal",
"green" => "Green"
]
],
"imagetype" => [
"display" => "Type",
"option" => [
"any" => "Any type",
"animatedgif" => "Animated GIF",
"photo" => "Photograph",
"transparent" => "Transparent"
]
],
"license" => [
"display" => "License",
"option" => [
"any" => "Any license",
"share" => "Non-commercial reproduction and sharing",
"sharecommercially" => "Reproduction and sharing",
"modify" => "Non-commercial reproduction, sharing and modification",
"modifycommercially" => "Reproduction, sharing and modification",
"public" => "Public domain"
]
]
]
);
break;
case "videos":
$base = array_merge(
$base,
[
"order" => [
"display" => "Order by",
"option" => [
"relevance" => "Relevance",
"views" => "Views",
"date" => "Most recent",
]
],
"source" => [
"display" => "Source",
"option" => [
"any" => "Any source",
"youtube" => "YouTube",
"dailymotion" => "Dailymotion",
]
]
]
);
break;
case "news":
$base = array_merge(
$base,
[
"time" => [
"display" => "Time posted",
"option" => [
"any" => "Any time",
"hour" => "Less than 1 hour ago",
"day" => "Past 24 hours",
"week" => "Past week",
"month" => "Past month"
]
],
"order" => [
"display" => "Order by",
"option" => [
"relevance" => "Relevance",
"date" => "Most recent"
]
]
]
);
break;
}
return $base;
}
private function get($proxy, $url, $get = []){
$headers = [
"User-Agent: " . config::USER_AGENT,
"Accept: application/json, text/plain, */*",
"Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip",
"DNT: 1",
"Connection: keep-alive",
"Origin: https://www.qwant.com",
"Referer: https://www.qwant.com/",
"Sec-Fetch-Dest: empty",
"Sec-Fetch-Mode: cors",
"Sec-Fetch-Site: same-site",
"TE: trailers"
];
$curlproc = curl_init();
if($get !== []){
$get = http_build_query($get);
$url .= "?" . $get;
}
curl_setopt($curlproc, CURLOPT_URL, $url);
curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
curl_setopt($curlproc, CURLOPT_HTTPHEADER, $headers);
// Bypass HTTP/2 check
curl_setopt($curlproc, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
$this->backend->assign_proxy($curlproc, $proxy);
$data = curl_exec($curlproc);
if(curl_errno($curlproc)){
throw new Exception(curl_error($curlproc));
}
curl_close($curlproc);
return $data;
}
public function web($get){
if($get["npt"]){
// get next page data
[$params, $proxy] = $this->backend->get($get["npt"], "web");
$params = json_decode($params, true);
}else{
// get _GET data instead
$search = $get["s"];
if(strlen($search) === 0){
throw new Exception("Search term is empty!");
}
if(strlen($search) > 2048){
throw new Exception("Search term is too long!");
}
$proxy = $this->backend->get_ip();
$params = [
"q" => $search,
"freshness" => $get["time"],
"count" => 10,
"locale" => $get["country"],
"offset" => 0,
"device" => "desktop",
"tgp" => 3,
"safesearch" => 0,
"displayed" => "true"
];
switch($get["nsfw"]){
case "yes": $params["safesearch"] = 0; break;
case "maybe": $params["safesearch"] = 1; break;
case "no": $params["safesearch"] = 2; break;
}
}
/*
$handle = fopen("scraper/qwant_web.json", "r");
$json = fread($handle, filesize("scraper/qwant_web.json"));
fclose($handle);*/
try{
$json =
$this->get(
$proxy,
"https://fdn.qwant.com/v3/search/web",
$params
);
}catch(Exception $error){
throw new Exception("Could not fetch JSON");
}
$json = json_decode($json, true);
if($json === NULL){
throw new Exception("Failed to decode JSON");
}
if(isset($json["data"]["message"][0])){
throw new Exception("Server returned an error:\n" . $json["data"]["message"][0]);
}
$out = [
"status" => "ok",
"spelling" => [
"type" => "no_correction",
"using" => null,
"correction" => null
],
"npt" => null,
"answer" => [],
"web" => [],
"image" => [],
"video" => [],
"news" => [],
"related" => []
];
if(
$json["status"] != "success" &&
$json["data"]["error_code"] === 5
){
// no results
return $out;
}
$this->detect_errors($json);
if(!isset($json["data"]["result"]["items"]["mainline"])){
throw new Exception("Server did not return a result object");
}
// data is OK, parse
// get instant answer
if(
$get["extendedsearch"] == "yes" &&
isset($json["data"]["result"]["items"]["sidebar"][0]["endpoint"])
){
try{
$answer =
$this->get(
$proxy,
"https://api.qwant.com/v3" .
$json["data"]["result"]["items"]["sidebar"][0]["endpoint"],
[]
);
$answer = json_decode($answer, true);
if(
$answer === null ||
$answer["status"] != "success" ||
$answer["data"]["result"] === null
){
throw new Exception();
}
// parse answer
$out["answer"][] = [
"title" => $answer["data"]["result"]["title"],
"description" => [
[
"type" => "text",
"value" => $this->trimdots($answer["data"]["result"]["description"])
]
],
"url" => $answer["data"]["result"]["url"],
"thumb" =>
$answer["data"]["result"]["thumbnail"]["landscape"] == null ?
null :
$this->unshitimage(
$answer["data"]["result"]["thumbnail"]["landscape"],
false
),
"table" => [],
"sublink" => []
];
}catch(Exception $error){
// do nothing in case of failure
}
}
// get word correction
if(isset($json["data"]["query"]["queryContext"]["alteredQuery"])){
$out["spelling"] = [
"type" => "including",
"using" => $json["data"]["query"]["queryContext"]["alteredQuery"],
"correction" => $json["data"]["query"]["queryContext"]["alterationOverrideQuery"]
];
}
// check for next page
if($json["data"]["result"]["lastPage"] === false){
$params["offset"] = $params["offset"] + 10;
$out["npt"] =
$this->backend->store(
json_encode($params),
"web",
$proxy
);
}
// parse results
foreach($json["data"]["result"]["items"]["mainline"] as $item){
switch($item["type"]){ // ignores ads
case "web":
$first_iteration = true;
foreach($item["items"] as $result){
if(isset($result["thumbnailUrl"])){
$thumb = [
"url" => $this->unshitimage($result["thumbnailUrl"]),
"ratio" => "16:9"
];
}else{
$thumb = [
"url" => null,
"ratio" => null
];
}
$sublinks = [];
if(isset($result["links"])){
foreach($result["links"] as $link){
$sublinks[] = [
"title" => $this->trimdots($link["title"]),
"date" => null,
"description" => isset($link["desc"]) ? $this->trimdots($link["desc"]) : null,
"url" => $link["url"]
];
}
}
// detect gibberish results
if(
$first_iteration &&
!isset($result["urlPingSuffix"])
){
throw new Exception("Qwant returned gibberish results");
}
$out["web"][] = [
"title" => $this->trimdots($result["title"]),
"description" => $this->trimdots($result["desc"]),
"url" => $result["url"],
"date" => null,
"type" => "web",
"thumb" => $thumb,
"sublink" => $sublinks,
"table" => []
];
$first_iteration = false;
}
break;
case "images":
foreach($item["items"] as $image){
$out["image"][] = [
"title" => $image["title"],
"source" => [
[
"url" => $image["media"],
"width" => (int)$image["width"],
"height" => (int)$image["height"]
],
[
"url" => $this->unshitimage($image["thumbnail"]),
"width" => $image["thumb_width"],
"height" => $image["thumb_height"]
]
],
"url" => $image["url"]
];
}
break;
case "videos":
foreach($item["items"] as $video){
$out["video"][] = [
"title" => $video["title"],
"description" => null,
"date" => (int)$video["date"],
"duration" => $video["duration"] === null ? null : $video["duration"] / 1000,
"views" => null,
"thumb" =>
$video["thumbnail"] === null ?
[
"url" => null,
"ratio" => null,
] :
[
"url" => $this->unshitimage($video["thumbnail"]),
"ratio" => "16:9",
],
"url" => $video["url"]
];
}
break;
case "related_searches":
foreach($item["items"] as $related){
$out["related"][] = $related["text"];
}
break;
}
}
return $out;
}
public function image($get){
if($get["npt"]){
[$params, $proxy] =
$this->backend->get(
$get["npt"],
"images"
);
$params = json_decode($params, true);
}else{
$search = $get["s"];
if(strlen($search) === 0){
throw new Exception("Search term is empty!");
}
$proxy = $this->backend->get_ip();
$params = [
"t" => "images",
"q" => $search,
"count" => 125,
"locale" => $get["country"],
"offset" => 0, // increment by 125
"device" => "desktop",
"tgp" => 3
];
if($get["time"] != "any"){
$params["freshness"] = $get["time"];
}
foreach(["size", "color", "imagetype", "license"] as $p){
if($get[$p] != "any"){
$params[$p] = $get[$p];
}
}
switch($get["nsfw"]){
case "yes": $params["safesearch"] = 0; break;
case "maybe": $params["safesearch"] = 1; break;
case "no": $params["safesearch"] = 2; break;
}
}
try{
$json = $this->get(
$proxy,
"https://api.qwant.com/v3/search/images",
$params,
);
}catch(Exception $err){
throw new Exception("Failed to get JSON");
}
/*
$handle = fopen("scraper/yandex.json", "r");
$json = fread($handle, filesize("scraper/yandex.json"));
fclose($handle);*/
$json = json_decode($json, true);
if($json === null){
throw new Exception("Failed to decode JSON");
}
$this->detect_errors($json);
if(isset($json["data"]["result"]["items"]["mainline"])){
throw new Exception("Qwant returned gibberish results");
}
$out = [
"status" => "ok",
"npt" => null,
"image" => []
];
if($json["data"]["result"]["lastPage"] === false){
$params["offset"] = $params["offset"] + 125;
$out["npt"] = $this->backend->store(
json_encode($params),
"images",
$proxy
);
}
foreach($json["data"]["result"]["items"] as $image){
$out["image"][] = [
"title" => $this->trimdots($image["title"]),
"source" => [
[
"url" => $image["media"],
"width" => $image["width"],
"height" => $image["height"]
],
[
"url" => $this->unshitimage($image["thumbnail"]),
"width" => $image["thumb_width"],
"height" => $image["thumb_height"]
]
],
"url" => $image["url"]
];
}
return $out;
}
public function video($get){
$search = $get["s"];
if(strlen($search) === 0){
throw new Exception("Search term is empty!");
}
$params = [
"t" => "videos",
"q" => $search,
"count" => 50,
"locale" => $get["country"],
"offset" => 0, // dont implement pagination
"device" => "desktop",
"tgp" => 3
];
switch($get["nsfw"]){
case "yes": $params["safesearch"] = 0; break;
case "maybe": $params["safesearch"] = 1; break;
case "no": $params["safesearch"] = 2; break;
}
try{
$json =
$this->get(
$this->backend->get_ip(),
"https://api.qwant.com/v3/search/videos",
$params
);
}catch(Exception $error){
throw new Exception("Could not fetch JSON");
}
/*
$handle = fopen("scraper/yandex-video.json", "r");
$json = fread($handle, filesize("scraper/yandex-video.json"));
fclose($handle);
*/
$json = json_decode($json, true);
if($json === null){
throw new Exception("Could not parse JSON");
}
$this->detect_errors($json);
if(isset($json["data"]["result"]["items"]["mainline"])){
throw new Exception("Qwant returned gibberish results");
}
$out = [
"status" => "ok",
"npt" => null,
"video" => [],
"author" => [],
"livestream" => [],
"playlist" => [],
"reel" => []
];
foreach($json["data"]["result"]["items"] as $video){
if(empty($video["thumbnail"])){
$thumb = [
"url" => null,
"ratio" => null
];
}else{
$thumb = [
"url" => $this->unshitimage($video["thumbnail"], false),
"ratio" => "16:9"
];
}
$duration = (int)$video["duration"];
$out["video"][] = [
"title" => $video["title"],
"description" => $this->limitstrlen($video["desc"]),
"author" => [
"name" => $video["channel"],
"url" => null,
"avatar" => null
],
"date" => (int)$video["date"],
"duration" => $duration === 0 ? null : $duration,
"views" => null,
"thumb" => $thumb,
"url" => preg_replace("/\?syndication=.+/", "", $video["url"])
];
}
return $out;
}
public function news($get){
$search = $get["s"];
if(strlen($search) === 0){
throw new Exception("Search term is empty!");
}
$params = [
"t" => "news",
"q" => $search,
"count" => 50,
"locale" => $get["country"],
"offset" => 0, // dont implement pagination
"device" => "desktop",
"tgp" => 3
];
switch($get["nsfw"]){
case "yes": $params["safesearch"] = 0; break;
case "maybe": $params["safesearch"] = 1; break;
case "no": $params["safesearch"] = 2; break;
}
try{
$json =
$this->get(
$this->backend->get_ip(),
"https://api.qwant.com/v3/search/news",
$params
);
}catch(Exception $error){
throw new Exception("Could not fetch JSON");
}
/*
$handle = fopen("scraper/yandex-video.json", "r");
$json = fread($handle, filesize("scraper/yandex-video.json"));
fclose($handle);
*/
$json = json_decode($json, true);
if($json === null){
throw new Exception("Could not parse JSON");
}
$this->detect_errors($json);
if(isset($json["data"]["result"]["items"]["mainline"])){
throw new Exception("Qwant returned gibberish results");
}
$out = [
"status" => "ok",
"npt" => null,
"news" => []
];
foreach($json["data"]["result"]["items"] as $news){
if(empty($news["media"][0]["pict_big"]["url"])){
$thumb = [
"url" => null,
"ratio" => null
];
}else{
$thumb = [
"url" => $this->unshitimage($news["media"][0]["pict_big"]["url"], false),
"ratio" => "16:9"
];
}
$out["news"][] = [
"title" => $news["title"],
"author" => $news["press_name"],
"description" => $this->trimdots($news["desc"]),
"date" => (int)$news["date"],
"thumb" => $thumb,
"url" => $news["url"]
];
}
return $out;
}
private function detect_errors($json){
if(
isset($json["status"]) &&
$json["status"] == "error"
){
if(isset($json["data"]["error_data"]["captchaUrl"])){
throw new Exception("Qwant returned a captcha");
}elseif(isset($json["data"]["error_data"]["error_code"])){
throw new Exception(
"Qwant returned an API error: " .
$json["data"]["error_data"]["error_code"]
);
}
throw new Exception("Qwant returned an API error");
}
}
private function limitstrlen($text){
return explode("\n", wordwrap($text, 300, "\n"))[0];
}
private function trimdots($text){
return trim($text, ". ");
}
private function unshitimage($url, $is_bing = true){
// https://s1.qwant.com/thumbr/0x0/8/d/f6de4deb2c2b12f55d8bdcaae576f9f62fd58a05ec0feeac117b354d1bf5c2/th.jpg?u=https%3A%2F%2Fwww.bing.com%2Fth%3Fid%3DOIP.vvDWsagzxjoKKP_rOqhwrQAAAA%26w%3D160%26h%3D160%26c%3D7%26pid%3D5.1&q=0&b=1&p=0&a=0
parse_str(parse_url($url)["query"], $parts);
if($is_bing){
$parse = parse_url($parts["u"]);
parse_str($parse["query"], $parts);
return "https://" . $parse["host"] . "/th?id=" . urlencode($parts["id"]);
}
return $parts["u"];
}
}
+69 -47
View File
@@ -135,6 +135,8 @@ class sc{
$type = $get["type"]; $type = $get["type"];
$proxy = $this->backend->get_ip(); $proxy = $this->backend->get_ip();
// token is not tied to an IP address
$token = $this->get_token($proxy); $token = $this->get_token($proxy);
switch($type){ switch($type){
@@ -149,7 +151,7 @@ class sc{
"limit" => 20, "limit" => 20,
"offset" => 0, "offset" => 0,
"linked_partitioning" => 1, "linked_partitioning" => 1,
"app_version" => 1713542117, "app_version" => 1769170554,
"app_locale" => "en" "app_locale" => "en"
]; ];
break; break;
@@ -164,7 +166,7 @@ class sc{
"limit" => 20, "limit" => 20,
"offset" => 0, "offset" => 0,
"linked_partitioning" => 1, "linked_partitioning" => 1,
"app_version" => 1713542117, "app_version" => 1769170554,
"app_locale" => "en" "app_locale" => "en"
]; ];
break; break;
@@ -179,7 +181,7 @@ class sc{
"limit" => 20, "limit" => 20,
"offset" => 0, "offset" => 0,
"linked_partitioning" => 1, "linked_partitioning" => 1,
"app_version" => 1713542117, "app_version" => 1769170554,
"app_locale" => "en" "app_locale" => "en"
]; ];
break; break;
@@ -194,7 +196,7 @@ class sc{
"limit" => 20, "limit" => 20,
"offset" => 0, "offset" => 0,
"linked_partitioning" => 1, "linked_partitioning" => 1,
"app_version" => 1713542117, "app_version" => 1769170554,
"app_locale" => "en" "app_locale" => "en"
]; ];
break; break;
@@ -209,7 +211,7 @@ class sc{
"limit" => 20, "limit" => 20,
"offset" => 0, "offset" => 0,
"linked_partitioning" => 1, "linked_partitioning" => 1,
"app_version" => 1713542117, "app_version" => 1769170554,
"app_locale" => "en" "app_locale" => "en"
]; ];
break; break;
@@ -225,7 +227,7 @@ class sc{
"limit" => 20, "limit" => 20,
"offset" => 0, "offset" => 0,
"linked_partitioning" => 1, "linked_partitioning" => 1,
"app_version" => 1713542117, "app_version" => 1769170554,
"app_locale" => "en" "app_locale" => "en"
]; ];
break; break;
@@ -247,17 +249,34 @@ class sc{
fclose($handle); fclose($handle);
*/ */
$this->fuckhtml->load($json);
// detect for cloudfront error
$title =
$this->fuckhtml
->getElementsByTagName(
"title"
);
if(
count($title) !== 0 &&
str_contains($title[0]["innerHTML"], "The request could not be satisfied")
){
throw new Exception("Ratelimited by Cloudfront, add some working proxies");
}
$json = json_decode($json, true); $json = json_decode($json, true);
if($json === null){ if($json === null){
if($last_attempt === true){ if($last_attempt === true){
throw new Exception("Fetched an invalid token (please report!!)"); throw new Exception("Fetched an invalid token");
} }
// token might've expired, get a new one and re-try search // token might've expired, get a new one and re-try search
$this->get_token($proxy); $token = $this->get_token($proxy, true);
return $this->music($get, true); return $this->music($get, true);
} }
@@ -378,7 +397,12 @@ class sc{
break; break;
case "track": case "track":
if(stripos($item["monetization_model"], "TIER") === false){ $stream = [
"endpoint" => null,
"url" => null
];
/*if(stripos($item["monetization_model"], "TIER") === false){
$stream = [ $stream = [
"endpoint" => "sc", "endpoint" => "sc",
@@ -394,7 +418,7 @@ class sc{
"endpoint" => null, "endpoint" => null,
"url" => null "url" => null
]; ];
} }*/
// parse track // parse track
$out["song"][] = [ $out["song"][] = [
@@ -422,16 +446,19 @@ class sc{
return $out; return $out;
} }
public function get_token($proxy){ public function get_token($proxy, $force_refresh = false){
$token = apcu_fetch("sc_token"); $token = apcu_fetch("sc_token");
if($token !== false){ if(
$token !== false &&
$force_refresh === false
){
return $token; return $token;
} }
// search through all javascript components on the main page // get token from main page
try{ try{
$html = $html =
$this->get( $this->get(
@@ -447,49 +474,44 @@ class sc{
$this->fuckhtml->load($html); $this->fuckhtml->load($html);
$scripts = $json =
$this->fuckhtml preg_split(
->getElementsByTagName( '/window.__sc_hydration ?= ?/',
"script" $html
); );
foreach($scripts as $script){ if(!isset($json[1])){
throw new Exception("Failed to find JSON containing token");
}
$json =
json_decode(
$this->fuckhtml
->extract_json(
$json[1]
),
true
);
if($json === null){
throw new Exception("Failed to decode JSON containing token");
}
foreach($json as $item){
if( if(
!isset($script["attributes"]["src"]) || isset($item["hydratable"]) &&
strpos($script["attributes"]["src"], "sndcdn.com") === false isset($item["data"]["id"])
){ ){
continue; apcu_store("sc_token", $item["data"]["id"]);
} return $item["data"]["id"];
try{
$js =
$this->get(
$proxy,
$script["attributes"]["src"],
[]
);
}catch(Exception $error){
throw new Exception("Failed to fetch search token");
}
preg_match(
'/client_id=([^"]+)/',
$js,
$token
);
if(isset($token[1])){
apcu_store("sc_token", $token[1]);
return $token[1];
break;
} }
} }
throw new Exception("Did not find a Soundcloud token in the Javascript blobs"); throw new Exception("Failed to find a valid token");
} }
private function limitstrlen($text){ private function limitstrlen($text){
+541
View File
@@ -0,0 +1,541 @@
<?php
class sepiasearch{
public function __construct(){
include "lib/backend.php";
$this->backend = new backend("sepiasearch");
}
public function getfilters($page){
return [
"nsfw" => [
"display" => "NSFW",
"option" => [
"yes" => "Yes", // &sensitiveContent=both
"no" => "No" // &sensitiveContent=false
]
],
"language" => [
"display" => "Language", // &language=
"option" => [
"any" => "Any language",
"en" => "English",
"fr" => "Français",
"ar" => "العربية",
"ca" => "Català",
"cs" => "Čeština",
"de" => "Deutsch",
"el" => "ελληνικά",
"eo" => "Esperanto",
"es" => "Español",
"eu" => "Euskara",
"fa" => "فارسی",
"fi" => "Suomi",
"gd" => "Gàidhlig",
"gl" => "Galego",
"hr" => "Hrvatski",
"hu" => "Magyar",
"is" => "Íslenska",
"it" => "Italiano",
"ja" => "日本語",
"kab" => "Taqbaylit",
"nl" => "Nederlands",
"no" => "Norsk",
"oc" => "Occitan",
"pl" => "Polski",
"pt" => "Português (Brasil)",
"pt-PT" => "Português (Portugal)",
"ru" => "Pусский",
"sk" => "Slovenčina",
"sq" => "Shqip",
"sv" => "Svenska",
"th" => "ไทย",
"tok" => "Toki Pona",
"tr" => "Türkçe",
"uk" => "украї́нська мо́ва",
"vi" => "Tiếng Việt",
"zh-Hans" => "简体中文(中国)",
"zh-Hant" => "繁體中文(台灣)"
]
],
"type" => [
"display" => "Result type", // i handle this
"option" => [
"videos" => "Videos",
"playlists" => "Playlists",
"channels" => "Channels"
]
],
"sort" => [
"display" => "Sort by",
"option" => [
"best" => "Best match", // no filter
"-publishedAt" => "Newest", // sort=-publishedAt
"publishedAt" => "Oldest" // sort=publishedAt
]
],
"newer" => [ // &startDate=2025-07-26T04:00:00.000Z
"display" => "Newer than",
"option" => "_DATE"
],
"duration" => [
"display" => "Duration",
"option" => [
"any" => "Any duration",
"short" => "Short (0-4mins)", // &durationRange=short
"medium" => "Medium (4-10 mins)",
"long" => "Long (10+ mins)",
]
],
"category" => [
"display" => "Category", // &categoryOneOf[]=
"option" => [
"any" => "Any category",
"1" => "Music",
"2" => "Films",
"3" => "Vehicles",
"4" => "Art",
"5" => "Sports",
"6" => "Travels",
"7" => "Gaming",
"8" => "People",
"9" => "Comedy",
"10" => "Entertainment",
"11" => "News & Politics",
"12" => "How To",
"13" => "Education",
"14" => "Activism",
"15" => "Science & Technology",
"16" => "Animals",
"17" => "Kids",
"18" => "Food"
]
],
"display" => [
"display" => "Display",
"option" => [
"any" => "Everything",
"true" => "Live videos", // &isLive=true
"false" => "VODs" // &isLive=false
]
],
"license" => [
"display" => "License", // &license=
"option" => [
"any" => "Any license",
"1" => "Attribution",
"2" => "Attribution - Share Alike",
"3" => "Attribution - No Derivatives",
"4" => "Attribution - Non Commercial",
"5" => "Attribution - Non Commercial - Share Alike",
"6" => "Attribution - Non Commercial - No Derivatives",
"7" => "Public Domain Dedication"
]
]
];
}
private function get($proxy, $url, $get = []){
$curlproc = curl_init();
if($get !== []){
$get = http_build_query($get);
$url .= "?" . $get;
}
curl_setopt($curlproc, CURLOPT_URL, $url);
curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
curl_setopt(
$curlproc,
CURLOPT_HTTPHEADER,
["User-Agent: " . config::USER_AGENT,
"Accept: application/json, text/plain, */*",
"Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip, deflate, br, zstd",
"DNT: 1",
"Sec-GPC: 1",
"Connection: keep-alive",
"Referer: https://sepiasearch.org/search",
"Sec-Fetch-Dest: empty",
"Sec-Fetch-Mode: cors",
"Sec-Fetch-Site: same-origin",
"Priority: u=0",
"TE: trailers"]
);
curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
$this->backend->assign_proxy($curlproc, $proxy);
$data = curl_exec($curlproc);
if(curl_errno($curlproc)){
throw new Exception(curl_error($curlproc));
}
curl_close($curlproc);
return $data;
}
public function video($get){
if($get["npt"]){
[$npt, $proxy] =
$this->backend
->get(
$get["npt"],
"videos"
);
$npt = json_decode($npt, true);
$type = $npt["type"];
$npt = $npt["npt"];
}else{
$proxy = $this->backend->get_ip();
$npt = [
"search" => $get["s"],
"start" => 0,
"count" => 20
];
if($get["type"] == "videos"){
//
// Parse video filters
//
switch($get["nsfw"]){
case "yes": $npt["nsfw"] = "both"; break;
case "no": $npt["nsfw"] = "false"; break;
}
$npt["boostLanguages[]"] = "en";
if($get["language"] != "any"){
$npt["languageOneOf[]"] = $get["language"];
}
if($get["sort"] != "best"){
$npt["sort"] = $get["sort"];
}
if($get["newer"] !== false){
$date = new DateTime("@{$get["newer"]}");
$date->setTimezone(new DateTimeZone("UTC"));
$formatted = $date->format("Y-m-d\TH:i:s.000\Z");
$npt["startDate"] = $formatted;
}
switch($get["duration"]){
case "short":
$npt["durationMax"] = 240;
break;
case "medium":
$npt["durationMin"] = 240;
$npt["durationMax"] = 600;
break;
case "long":
$npt["durationMin"] = 600;
break;
}
if($get["category"] != "any"){
$npt["categoryOneOf[]"] = $get["category"];
}
if($get["display"] != "any"){
$npt["isLive"] = $get["display"];
}
if($get["license"] != "any"){
// typo in license, lol
$npt["licenceOneOf[]"] = $get["license"];
}
}
$type = $get["type"];
}
switch($type){
case "videos":
$url = "https://sepiasearch.org/api/v1/search/videos";
break;
case "channels":
$url = "https://sepiasearch.org/api/v1/search/video-channels";
break;
case "playlists":
$url = "https://sepiasearch.org/api/v1/search/video-playlists";
break;
}
//$json = file_get_contents("scraper/sepia.json");
try{
$json =
$this->get(
$proxy,
$url,
$npt
);
}catch(Exception $error){
throw new Exception("Failed to fetch JSON");
}
$json = json_decode($json, true);
if($json === null){
throw new Exception("Failed to parse JSON");
}
if(isset($json["errors"])){
$msg = [];
foreach($json["errors"] as $error){
if(isset($error["msg"])){
$msg[] = $error["msg"];
}
}
throw new Exception("Sepia Search returned error(s): " . implode(", ", $msg));
}
if(!isset($json["data"])){
throw new Exception("Sepia Search did not return a data object");
}
$out = [
"status" => "ok",
"npt" => null,
"video" => [],
"author" => [],
"livestream" => [],
"playlist" => [],
"reel" => []
];
switch($get["type"]){
case "videos":
foreach($json["data"] as $video){
if(count($video["account"]["avatars"]) !== 0){
$avatar =
$video["account"]["avatars"][count($video["account"]["avatars"]) - 1]["url"];
}else{
$avatar = null;
}
if($video["thumbnailUrl"] === null){
$thumb = [
"ratio" => null,
"url" => null
];
}else{
$thumb = [
"ratio" => "16:9",
"url" => $video["thumbnailUrl"]
];
}
if($video["isLive"]){
$append = "livestream";
}else{
$append = "video";
}
$out[$append][] = [
"title" => $video["name"],
"description" =>
$this->limitstrlen(
$this->titledots(
$video["description"]
)
),
"author" => [
"name" => $video["account"]["displayName"] . " ({$video["account"]["name"]})",
"url" => $video["account"]["url"],
"avatar" => $avatar
],
"date" => strtotime($video["publishedAt"]),
"duration" => $video["isLive"] ? "_LIVE" : $video["duration"],
"views" => $video["views"],
"thumb" => $thumb,
"url" => $video["url"]
];
}
break;
case "playlists":
foreach($json["data"] as $playlist){
if(count($playlist["ownerAccount"]["avatars"]) !== 0){
$avatar =
$playlist["ownerAccount"]["avatars"][count($playlist["ownerAccount"]["avatars"]) - 1]["url"];
}else{
$avatar = null;
}
if($playlist["thumbnailUrl"] === null){
$thumb = [
"ratio" => null,
"url" => null
];
}else{
$thumb = [
"ratio" => "16:9",
"url" => $playlist["thumbnailUrl"]
];
}
$out["playlist"][] = [
"title" => $playlist["displayName"],
"description" =>
$this->limitstrlen(
$this->titledots(
$playlist["description"]
)
),
"author" => [
"name" => $playlist["ownerAccount"]["displayName"] . " ({$playlist["ownerAccount"]["name"]})",
"url" => $playlist["ownerAccount"]["url"],
"avatar" => $avatar
],
"date" => strtotime($playlist["createdAt"]),
"duration" => $playlist["videosLength"],
"views" => null,
"thumb" => $thumb,
"url" => $playlist["url"]
];
}
break;
case "channels":
foreach($json["data"] as $channel){
if(count($channel["avatars"]) !== 0){
$thumb = [
"ratio" => "1:1",
"url" => $channel["avatars"][count($channel["avatars"]) - 1]["url"]
];
}else{
$thumb = [
"ratio" => null,
"url" => null
];
}
$out["author"][] = [
"title" => $channel["displayName"] . " ({$channel["name"]})",
"followers" => $channel["followersCount"],
"description" =>
$channel["videosCount"] . " videos. " .
$this->limitstrlen(
$this->titledots(
$channel["description"]
)
),
"thumb" => $thumb,
"url" => $channel["url"]
];
}
break;
}
// get next page
if($json["total"] - 20 > $npt["start"]){
$npt["start"] += 20;
$npt = [
"type" => $get["type"],
"npt" => $npt
];
$out["npt"] =
$this->backend
->store(
json_encode($npt),
"videos",
$proxy
);
}
return $out;
}
private function titledots($title){
$substr = substr($title, -3);
if(
$substr == "..." ||
$substr == "…"
){
return trim(substr($title, 0, -3), " \n\r\t\v\x00\0\x0B\xc2\xa0");
}
return trim($title, " \n\r\t\v\x00\0\x0B\xc2\xa0");
}
private function limitstrlen($text){
return
explode(
"\n",
wordwrap(
str_replace(
["\n\r", "\r\n", "\n", "\r"],
" ",
$text
),
300,
"\n"
),
2
)[0];
}
}
+307 -370
View File
@@ -9,6 +9,10 @@ class startpage{
include "lib/fuckhtml.php"; include "lib/fuckhtml.php";
$this->fuckhtml = new fuckhtml(); $this->fuckhtml = new fuckhtml();
include "lib/anubis.php";
$this->anubis = new anubis();
$this->anubis_error = "all gucci";
} }
public function getfilters($page){ public function getfilters($page){
@@ -244,7 +248,7 @@ class startpage{
} }
} }
private function get($proxy, $url, $get = [], $post = false, $is_xhr = false){ private function get($proxy, $url, $get = [], $post = false, $is_xhr = false, $return_cookies = false, $passed_cookies = []){
$curlproc = curl_init(); $curlproc = curl_init();
@@ -259,6 +263,27 @@ class startpage{
$url .= "?" . $get; $url .= "?" . $get;
} }
if($return_cookies){
$cookies = [];
curl_setopt($curlproc, CURLOPT_HEADERFUNCTION, function($curlproc, $header) use (&$cookies){
$length = strlen($header);
$header = explode(":", $header, 2);
if(trim(strtolower($header[0])) == "set-cookie"){
$cookie_tmp = explode("=", trim($header[1]), 2);
$cookies[trim($cookie_tmp[0])] =
explode(";", $cookie_tmp[1], 2)[0];
}
return $length;
});
}
curl_setopt($curlproc, CURLOPT_URL, $url); curl_setopt($curlproc, CURLOPT_URL, $url);
// http2 bypass // http2 bypass
@@ -266,6 +291,15 @@ class startpage{
curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
$passed_cookies["preferences"] = "date_timeEEEworldN1Ndisable_family_filterEEE1N1Ndisable_open_in_new_windowEEE0N1Nenable_post_methodEEE1N1Nenable_proxy_safety_suggestEEE0N1Nenable_stay_controlEEE0N1Ninstant_answersEEE1N1Nlang_homepageEEEs%2Fdevice%2FenN1NlanguageEEEenglishN1Nlanguage_uiEEEenglishN1Nnum_of_resultsEEE20N1Nsearch_results_regionEEEallN1NsuggestionsEEE1N1Nwt_unitEEEcelsius";
foreach($passed_cookies as $name => $val){
$header_cookies[] = "$name=$val";
}
$header_cookies = rtrim(implode("; ", $header_cookies));
if($is_xhr === true){ if($is_xhr === true){
curl_setopt($curlproc, CURLOPT_HTTPHEADER, curl_setopt($curlproc, CURLOPT_HTTPHEADER,
@@ -279,7 +313,7 @@ class startpage{
"Origin: https://www.startpage.com/", "Origin: https://www.startpage.com/",
"DNT: 1", "DNT: 1",
"Connection: keep-alive", "Connection: keep-alive",
"Cookie: preferences=date_timeEEEworldN1Ndisable_family_filterEEE1N1Ndisable_open_in_new_windowEEE0N1Nenable_post_methodEEE1N1Nenable_proxy_safety_suggestEEE0N1Nenable_stay_controlEEE0N1Ninstant_answersEEE1N1Nlang_homepageEEEs%2Fdevice%2FenN1NlanguageEEEenglishN1Nlanguage_uiEEEenglishN1Nnum_of_resultsEEE20N1Nsearch_results_regionEEEallN1NsuggestionsEEE1N1Nwt_unitEEEcelsius", "Cookie: $header_cookies",
"Sec-Fetch-Dest: empty", "Sec-Fetch-Dest: empty",
"Sec-Fetch-Mode: cors", "Sec-Fetch-Mode: cors",
"Sec-Fetch-Site: same-origin", "Sec-Fetch-Site: same-origin",
@@ -298,7 +332,7 @@ class startpage{
"Content-Length: " . strlen($get), "Content-Length: " . strlen($get),
"DNT: 1", "DNT: 1",
"Connection: keep-alive", "Connection: keep-alive",
"Cookie: preferences=date_timeEEEworldN1Ndisable_family_filterEEE1N1Ndisable_open_in_new_windowEEE0N1Nenable_post_methodEEE1N1Nenable_proxy_safety_suggestEEE0N1Nenable_stay_controlEEE0N1Ninstant_answersEEE1N1Nlang_homepageEEEs%2Fdevice%2FenN1NlanguageEEEenglishN1Nlanguage_uiEEEenglishN1Nnum_of_resultsEEE20N1Nsearch_results_regionEEEallN1NsuggestionsEEE1N1Nwt_unitEEEcelsius", "Cookie: $header_cookies",
"Upgrade-Insecure-Requests: 1", "Upgrade-Insecure-Requests: 1",
"Sec-Fetch-Dest: document", "Sec-Fetch-Dest: document",
"Sec-Fetch-Mode: navigate", "Sec-Fetch-Mode: navigate",
@@ -316,7 +350,7 @@ class startpage{
"Accept-Encoding: gzip", "Accept-Encoding: gzip",
"DNT: 1", "DNT: 1",
"Connection: keep-alive", "Connection: keep-alive",
"Cookie: preferences=date_timeEEEworldN1Ndisable_family_filterEEE1N1Ndisable_open_in_new_windowEEE0N1Nenable_post_methodEEE1N1Nenable_proxy_safety_suggestEEE0N1Nenable_stay_controlEEE0N1Ninstant_answersEEE1N1Nlang_homepageEEEs%2Fdevice%2FenN1NlanguageEEEenglishN1Nlanguage_uiEEEenglishN1Nnum_of_resultsEEE20N1Nsearch_results_regionEEEallN1NsuggestionsEEE1N1Nwt_unitEEEcelsius", "Cookie: $header_cookies",
"Sec-Fetch-Dest: document", "Sec-Fetch-Dest: document",
"Sec-Fetch-Mode: navigate", "Sec-Fetch-Mode: navigate",
"Sec-Fetch-Site: none", "Sec-Fetch-Site: none",
@@ -342,6 +376,15 @@ class startpage{
} }
curl_close($curlproc); curl_close($curlproc);
if($return_cookies){
return [
"cookies" => $cookies,
"data" => $data
];
}
return $data; return $data;
} }
@@ -349,14 +392,22 @@ class startpage{
if($get["npt"]){ if($get["npt"]){
$this->anubis_error = "Not ran on pagination";
[$post, $proxy] = $this->backend->get($get["npt"], "web"); [$post, $proxy] = $this->backend->get($get["npt"], "web");
$post = json_decode($post, true);
$query = $post["query"];
$cookies = $post["cookies"];
try{ try{
$html = $this->get( $html = $this->get(
$proxy, $proxy,
"https://www.startpage.com/sp/search", "https://www.startpage.com/sp/search",
$post, $query,
true true,
false,
false,
$cookies
); );
}catch(Exception $error){ }catch(Exception $error){
@@ -398,7 +449,11 @@ class startpage{
$html = $this->get( $html = $this->get(
$proxy, $proxy,
"https://www.startpage.com/sp/search", "https://www.startpage.com/sp/search",
$params $params,
false,
false,
true,
[]
); );
}catch(Exception $error){ }catch(Exception $error){
@@ -406,10 +461,41 @@ class startpage{
} }
//$html = file_get_contents("scraper/startpage.html"); //$html = file_get_contents("scraper/startpage.html");
// detect captcha on that page
$this->detect_captcha($html["data"]);
// solve anubis if need be
$cookies = $this->solve_anubis($proxy, $html);
if($cookies === false){
$cookies = [];
$html = $html["data"];
}else{
try{
$html = $this->get(
$proxy,
"https://www.startpage.com/sp/search",
$params,
false,
false,
false,
$cookies
);
}catch(Exception $error){
throw new Exception("Failed to re-fetch search page. Anubis error: {$this->anubis_error}");
}
}
} }
// detect captcha even if we passed anubis challenge
$this->detect_captcha($html); $this->detect_captcha($html);
//echo htmlspecialchars($html);
if( if(
preg_match( preg_match(
'/React\.createElement\(UIStartpage\.AppSerpWeb, ?(.+)\),?$/m', '/React\.createElement\(UIStartpage\.AppSerpWeb, ?(.+)\),?$/m',
@@ -418,14 +504,14 @@ class startpage{
) === 0 ) === 0
){ ){
throw new Exception("Failed to grep JSON object"); throw new Exception("Failed to grep JSON object. Anubis error: {$this->anubis_error}");
} }
$json = json_decode($matches[1], true); $json = json_decode($matches[1], true);
if($json === null){ if($json === null){
throw new Exception("Failed to decode JSON"); throw new Exception("Failed to decode JSON. Anubis error: {$this->anubis_error}");
} }
//print_r($json); //print_r($json);
@@ -447,7 +533,7 @@ class startpage{
]; ];
// get npt // get npt
$out["npt"] = $this->parse_npt($json, "web", $proxy); $out["npt"] = $this->parse_npt($json, "web", $proxy, $cookies);
foreach($json["render"]["presenter"]["regions"]["mainline"] as $category){ foreach($json["render"]["presenter"]["regions"]["mainline"] as $category){
@@ -564,12 +650,16 @@ class startpage{
break; break;
case "spellsuggest-google": case "spellsuggest-google":
if(isset($category["results"][0]["query"])){
$out["spelling"] = $out["spelling"] =
[ [
"type" => "including", "type" => "including",
"using" => $json["render"]["query"], "using" => rawurldecode($json["render"]["query"]),
"correction" => $category["results"][0]["query"] "correction" => html_entity_decode($category["results"][0]["query"])
]; ];
}
break; break;
case "dictionary-qi": case "dictionary-qi":
@@ -645,318 +735,6 @@ class startpage{
} }
} }
// parse instant answers
if(
$get["extendedsearch"] == "yes" &&
$get_instant_answer === true
){
// https://www.startpage.com/sp/qi?qimsn=ex&sxap=%2Fv1%2Fquery&sc=BqZ3inqrAgF701&sr=1
try{
$post = [
"se" => "n0vze2y9dqwy",
"q" => $json["render"]["query"],
"results" => [], // populate
"enableKnowledgePanel" => true,
"enableMediaThumbBar" => false,
"enableSearchSuggestions" => false,
"enableTripadvisorProperties" => [],
"enableTripadvisorPlaces" => [],
"enableTripadvisorPlacesForLocations" => [],
"enableWebProducts" => false,
"tripadvisorPartnerId" => null,
"tripadvisorMapColorMode" => "light",
"tripadvisorDisablesKnowledgePanel" => false,
"instantAnswers" => [
"smartAnswers",
"youtube",
"tripadvisor"
],
"iaType" => null,
"forceEnhancedKnowledgePanel" => false,
"shoppingOnly" => false,
"allowAdultProducts" => true,
"lang" => "en",
"browserLang" => "en-US",
"browserTimezone" => "America/New_York",
"market" => null,
"userLocation" => null,
"userDate" => date("Y-m-d"),
"userAgentType" => "unknown"
];
foreach($out["web"] as $result){
$post["results"][] = [
"url" => $result["url"],
"title" => $result["title"]
];
}
$post = json_encode($post, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_IGNORE);
$additional_data =
$this->get(
$proxy,
"https://www.startpage.com/sp/qi?qimsn=ex&sxap=%2Fv1%2Fquery&sc=" . $json["render"]["callback_sc"] . "&sr=1",
$post,
true,
true
);
$additional_data = json_decode($additional_data, true);
if($additional_data === null){
throw new Exception("Failed to decode JSON"); // just break out, dont fail completely
}
if(!isset($additional_data["knowledgePanel"])){
throw new Exception("Response has missing data (knowledgePanel)");
}
$additional_data = $additional_data["knowledgePanel"];
$answer = [
"title" => $additional_data["meta"]["title"],
"description" => [
[
"type" => "quote",
"value" => $additional_data["meta"]["description"]
]
],
"url" => $additional_data["meta"]["origWikiUrl"],
"thumb" => $additional_data["meta"]["image"],
"table" => [],
"sublink" => []
];
// parse html for instant answer
$this->fuckhtml->load($additional_data["html"]);
$div =
$this->fuckhtml
->getElementsByTagName(
"div"
);
// get description
$description =
$this->fuckhtml
->getElementsByClassName(
"sx-kp-short-extract sx-kp-short-extract-complete",
$div
);
if(count($description) !== 0){
$answer["description"][] = [
"type" => "text",
"value" =>
html_entity_decode(
$this->fuckhtml
->getTextContent(
$description[0]
)
)
];
}
// get socials
$socials =
$this->fuckhtml
->getElementsByClassName(
"sx-wiki-social-link",
"a"
);
foreach($socials as $social){
$title =
$this->fuckhtml
->getTextContent(
$social["attributes"]["title"]
);
$url =
$this->fuckhtml
->getTextContent(
$social["attributes"]["href"]
);
switch($title){
case "Official Website":
$title = "Website";
break;
}
$answer["sublink"][$title] = $url;
}
// get videos
$videos =
$this->fuckhtml
->getElementsByClassName(
"sx-kp-video-grid-item",
$div
);
foreach($videos as $video){
$this->fuckhtml->load($video);
$as =
$this->fuckhtml
->getElementsByTagName(
"a"
);
if(count($as) === 0){
// ?? invalid
continue;
}
$image =
$this->fuckhtml
->getElementsByAttributeName(
"data-sx-src",
"img"
);
if(count($image) !== 0){
$thumb = [
"ratio" => "16:9",
"url" =>
$this->fuckhtml
->getTextContent(
$image[0]["attributes"]["data-sx-src"]
)
];
}else{
$thumb = [
"ratio" => null,
"url" => null
];
}
$out["video"][] = [
"title" =>
$this->fuckhtml
->getTextContent(
$as[0]["attributes"]["title"]
),
"description" => null,
"date" => null,
"duration" => null,
"views" => null,
"thumb" => $thumb,
"url" =>
$this->fuckhtml
->getTextContent(
$as[0]["attributes"]["href"]
)
];
}
// reset
$this->fuckhtml->load($additional_data["html"]);
// get table elements
$table =
$this->fuckhtml
->getElementsByClassName(
"sx-infobox",
"table"
);
if(count($table) !== 0){
$trs =
$this->fuckhtml
->getElementsByTagName(
"tr"
);
foreach($trs as $tr){
$this->fuckhtml->load($tr);
// ok so startpage devs cant fucking code a table
// td = content
// th (AAAHH) = title
$tds =
$this->fuckhtml
->getElementsByTagName(
"td"
);
$ths =
$this->fuckhtml
->getElementsByTagName(
"th"
);
if(
count($ths) === 1 &&
count($tds) === 1
){
$title =
$this->fuckhtml
->getTextContent(
$ths[0]
);
$description = [];
$this->fuckhtml->load($tds[0]);
$lis =
$this->fuckhtml
->getElementsByTagName(
"li"
);
if(count($lis) !== 0){
foreach($lis as $li){
$description[] =
$this->fuckhtml
->getTextContent(
$li
);
}
$description = implode(", ", $description);
}else{
$description =
$this->fuckhtml
->getTextContent(
$tds[0]
);
}
$answer["table"][$title] = $description;
}
}
}
$out["answer"][] = $answer;
}catch(Exception $error){
// do nothing
//echo "error!";
}
}
return $out; return $out;
} }
@@ -964,14 +742,22 @@ class startpage{
if($get["npt"]){ if($get["npt"]){
$this->anubis_error = "Not ran on pagination";
[$post, $proxy] = $this->backend->get($get["npt"], "images"); [$post, $proxy] = $this->backend->get($get["npt"], "images");
$post = json_decode($post, true);
$query = $post["query"];
$cookies = $post["cookies"];
try{ try{
$html = $this->get( $html = $this->get(
$proxy, $proxy,
"https://www.startpage.com/sp/search", "https://www.startpage.com/sp/search",
$post, $query,
true true,
false,
false,
$cookies
); );
}catch(Exception $error){ }catch(Exception $error){
@@ -986,8 +772,6 @@ class startpage{
throw new Exception("Search term is empty!"); throw new Exception("Search term is empty!");
} }
try{
$proxy = $this->backend->get_ip(); $proxy = $this->backend->get_ip();
$params = [ $params = [
@@ -1045,17 +829,45 @@ class startpage{
$html = $this->get( $html = $this->get(
$proxy, $proxy,
"https://www.startpage.com/sp/search", "https://www.startpage.com/sp/search",
$params $params,
false,
false,
true,
[]
); );
}catch(Exception $error){ }catch(Exception $error){
throw new Exception("Failed to fetch search page"); throw new Exception("Failed to fetch search page");
} }
//$html = file_get_contents("scraper/startpage.html"); //$html = file_get_contents("scraper/startpage.html");
// detect captcha on that page
$this->detect_captcha($html["data"]);
// solve anubis if need be
$cookies = $this->solve_anubis($proxy, $html);
if($cookies === false){
$cookies = [];
$html = $html["data"];
}else{
try{
$html = $this->get(
$proxy,
"https://www.startpage.com/sp/search",
$params,
false,
false,
false,
$cookies
);
}catch(Exception $error){ }catch(Exception $error){
throw new Exception("Failed to fetch search page"); throw new Exception("Failed to re-fetch search page. Anubis error: {$this->anubis_error}");
}
} }
} }
@@ -1075,18 +887,18 @@ class startpage{
) === 0 ) === 0
){ ){
throw new Exception("Failed to grep JSON object"); throw new Exception("Failed to grep JSON object. Anubis error: {$this->anubis_error}");
} }
$json = json_decode($matches[1], true); $json = json_decode($matches[1], true);
if($json === null){ if($json === null){
throw new Exception("Failed to decode JSON object"); throw new Exception("Failed to decode JSON object. Anubis error: {$this->anubis_error}");
} }
// get npt // get npt
$out["npt"] = $this->parse_npt($json, "images", $proxy); $out["npt"] = $this->parse_npt($json, "images", $proxy, $cookies);
// get images // get images
foreach($json["render"]["presenter"]["regions"]["mainline"] as $category){ foreach($json["render"]["presenter"]["regions"]["mainline"] as $category){
@@ -1125,14 +937,22 @@ class startpage{
if($get["npt"]){ if($get["npt"]){
$this->anubis_error = "Not ran on pagination";
[$post, $proxy] = $this->backend->get($get["npt"], "videos"); [$post, $proxy] = $this->backend->get($get["npt"], "videos");
$post = json_decode($post, true);
$query = $post["query"];
$cookies = $post["cookies"];
try{ try{
$html = $this->get( $html = $this->get(
$proxy, $proxy,
"https://www.startpage.com/sp/search", "https://www.startpage.com/sp/search",
$post, $query,
true true,
false,
false,
$cookies
); );
}catch(Exception $error){ }catch(Exception $error){
@@ -1147,8 +967,6 @@ class startpage{
throw new Exception("Search term is empty!"); throw new Exception("Search term is empty!");
} }
try{
$proxy = $this->backend->get_ip(); $proxy = $this->backend->get_ip();
$params = [ $params = [
@@ -1176,17 +994,45 @@ class startpage{
$html = $this->get( $html = $this->get(
$proxy, $proxy,
"https://www.startpage.com/sp/search", "https://www.startpage.com/sp/search",
$params $params,
false,
false,
true,
[]
); );
}catch(Exception $error){ }catch(Exception $error){
throw new Exception("Failed to fetch search page"); throw new Exception("Failed to fetch search page");
} }
//$html = file_get_contents("scraper/startpage.html"); //$html = file_get_contents("scraper/startpage.html");
// detect captcha on that page
$this->detect_captcha($html["data"]);
// solve anubis if need be
$cookies = $this->solve_anubis($proxy, $html);
if($cookies === false){
$cookies = [];
$html = $html["data"];
}else{
try{
$html = $this->get(
$proxy,
"https://www.startpage.com/sp/search",
$params,
false,
false,
false,
$cookies
);
}catch(Exception $error){ }catch(Exception $error){
throw new Exception("Failed to fetch search page"); throw new Exception("Failed to re-fetch search page. Anubis error: {$this->anubis_error}");
}
} }
} }
@@ -1200,14 +1046,14 @@ class startpage{
) === 0 ) === 0
){ ){
throw new Exception("Failed to get JSON object"); throw new Exception("Failed to get JSON object. Anubis error: {$this->anubis_error}");
} }
$json = json_decode($matches[1], true); $json = json_decode($matches[1], true);
if($json === null){ if($json === null){
throw new Exception("Failed to decode JSON object"); throw new Exception("Failed to decode JSON object. Anubis error: {$this->anubis_error}");
} }
$out = [ $out = [
@@ -1221,12 +1067,17 @@ class startpage{
]; ];
// get npt // get npt
$out["npt"] = $this->parse_npt($json, "video", $proxy); $out["npt"] = $this->parse_npt($json, "video", $proxy, $cookies);
// get results // get results
foreach($json["render"]["presenter"]["regions"]["mainline"] as $category){ foreach($json["render"]["presenter"]["regions"]["mainline"] as $category){
if($category["display_type"] == "video-youtube"){ if(
preg_match(
'/^video-/i',
$category["display_type"]
)
){
foreach($category["results"] as $video){ foreach($category["results"] as $video){
@@ -1248,7 +1099,7 @@ class startpage{
} }
$out["video"][] = [ $out["video"][] = [
"title" => $video["title"], "title" => str_replace(["", ""], "", $video["title"]),
"description" => $this->limitstrlen($video["description"]), "description" => $this->limitstrlen($video["description"]),
"author" => [ "author" => [
"name" => $video["channelTitle"], "name" => $video["channelTitle"],
@@ -1256,7 +1107,7 @@ class startpage{
"avatar" => null "avatar" => null
], ],
"date" => strtotime($video["publishDate"]), "date" => strtotime($video["publishDate"]),
"duration" => $this->hms2int($video["duration"]), "duration" => $this->hms2int($category["display_type"] == "video-youtube" ? $video["duration"] : $video["duration"] / 1000),
"views" => (int)$video["viewCount"], "views" => (int)$video["viewCount"],
"thumb" => $thumb, "thumb" => $thumb,
"url" => $video["clickUrl"] "url" => $video["clickUrl"]
@@ -1272,14 +1123,22 @@ class startpage{
if($get["npt"]){ if($get["npt"]){
$this->anubis_error = "Not ran on pagination";
[$post, $proxy] = $this->backend->get($get["npt"], "news"); [$post, $proxy] = $this->backend->get($get["npt"], "news");
$post = json_decode($post, true);
$query = $post["query"];
$cookies = $post["cookies"];
try{ try{
$html = $this->get( $html = $this->get(
$proxy, $proxy,
"https://www.startpage.com/sp/search", "https://www.startpage.com/sp/search",
$post, $query,
true true,
false,
false,
$cookies
); );
}catch(Exception $error){ }catch(Exception $error){
@@ -1294,8 +1153,6 @@ class startpage{
throw new Exception("Search term is empty!"); throw new Exception("Search term is empty!");
} }
try{
$proxy = $this->backend->get_ip(); $proxy = $this->backend->get_ip();
$params = [ $params = [
@@ -1318,17 +1175,45 @@ class startpage{
$html = $this->get( $html = $this->get(
$proxy, $proxy,
"https://www.startpage.com/sp/search", "https://www.startpage.com/sp/search",
$params $params,
false,
false,
true,
[]
); );
}catch(Exception $error){ }catch(Exception $error){
throw new Exception("Failed to fetch search page"); throw new Exception("Failed to fetch search page. Anubis error: {$this->anubis_error}");
} }
//$html = file_get_contents("scraper/startpage.html"); //$html = file_get_contents("scraper/startpage.html");
// detect captcha on that page
$this->detect_captcha($html["data"]);
// solve anubis if need be
$cookies = $this->solve_anubis($proxy, $html);
if($cookies === false){
$cookies = [];
$html = $html["data"];
}else{
try{
$html = $this->get(
$proxy,
"https://www.startpage.com/sp/search",
$params,
false,
false,
false,
$cookies
);
}catch(Exception $error){ }catch(Exception $error){
throw new Exception("Failed to fetch search page"); throw new Exception("Failed to re-fetch search page. Anubis error: {$this->anubis_error}");
}
} }
} }
@@ -1342,14 +1227,14 @@ class startpage{
) === 0 ) === 0
){ ){
throw new Exception("Failed to get JSON object"); throw new Exception("Failed to get JSON object. Anubis error: {$this->anubis_error}");
} }
$json = json_decode($matches[1], true); $json = json_decode($matches[1], true);
if($json === null){ if($json === null){
throw new Exception("Failed to decode JSON object"); throw new Exception("Failed to decode JSON object. Anubis error: {$this->anubis_error}");
} }
$out = [ $out = [
@@ -1359,7 +1244,7 @@ class startpage{
]; ];
// get npt // get npt
$out["npt"] = $this->parse_npt($json, "news", $proxy); $out["npt"] = $this->parse_npt($json, "news", $proxy, $cookies);
foreach($json["render"]["presenter"]["regions"]["mainline"] as $category){ foreach($json["render"]["presenter"]["regions"]["mainline"] as $category){
@@ -1402,35 +1287,35 @@ class startpage{
return $out; return $out;
} }
private function parse_npt($json, $pagetype, $proxy){ private function parse_npt($json, $pagetype, $proxy, $cookies){
foreach($json["render"]["presenter"]["pagination"]["pages"] as $page){ foreach($json["render"]["presenter"]["pagination"]["pages"] as $page){
if($page["name"] == "Next"){ if($page["name"] == "Next"){
parse_str(
explode(
"?",
$page["url"],
2
)[1],
$str
);
return return
$this->backend->store( $this->backend->store(
json_encode([
"query" =>
http_build_query( http_build_query(
[ [
"lui" => "english", "lui" => "english",
"language" => "english", "language" => "english",
"query" => $str["q"], "sc" => $json["render"]["search_sc"],
"cat" => $pagetype,
"sc" => $str["sc"],
"t" => "device", "t" => "device",
"segment" => "startpage.udog", "cat" => $pagetype,
"page" => $str["page"] "segment" => "startpage.udog", // "original" sometimes
"abd" => "0",
"abe" => "0",
"query" => rawurldecode($json["render"]["query"]),
"page" => $page["number"],
"qsr" => "all",
"qadf" => "none", // @ todo fix (??)
...(isset($page["token"]) ? ["page_token" => $page["token"]] : [])
] ]
), ),
"cookies" => $cookies
]),
$pagetype, $pagetype,
$proxy $proxy
); );
@@ -1576,4 +1461,56 @@ class startpage{
throw new Exception("Startpage redirected the scraper to an unhandled page"); throw new Exception("Startpage redirected the scraper to an unhandled page");
} }
} }
private function solve_anubis($proxy, $html){
//
// solve anubis challenge, if theres one
//
$res = null;
$cookies = [];
try{
$res = $this->anubis->scrape($html["data"]);
}catch(Exception $error){
// do nothang
$this->anubis_error = $error->getMessage();
}
if($res !== null){
// https://www.startpage.com/.within.website/x/cmd/anubis/api/pass-challenge
// ?id=01a00241-da22-7d62-b64f-e7a6a0e8bda8
// &response=0000460826adc23e0363e97e713e4aa36ca46e1a7cd933184442772f3d6b994c
// &nonce=3098
// &redir=https%3A%2F%2Fwww.startpage.com%2Fsp%2Fsearch
// &elapsedTime=111
$anubis_response =
$this->get(
$proxy,
"https://www.startpage.com/.within.website/x/cmd/anubis/api/pass-challenge",
[
"id" => $res["id"],
"response" => $res["response"],
"nonce" => $res["nonce"],
"redir" => "https://www.startpage.com/sp/search",
"elapsedTime" => $res["elapsedTime"]
],
false,
false,
true,
$html["cookies"]
);
// re-fetch search page AGAIN with the anubis bullshit
$cookies = array_merge($anubis_response["cookies"], $html["cookies"]);
return $cookies;
}
return false;
}
} }
+252
View File
@@ -0,0 +1,252 @@
<?php
class swisscows{
public function __construct(){
include "lib/backend.php";
$this->backend = new backend("swisscows");
include "lib/fuckhtml.php";
$this->fuckhtml = new fuckhtml();
}
public function getfilters($page){
return [
"type" => [
"display" => "Type",
"option" => [
"track" => "Tracks",
"playlist" => "Playlists"
]
]
];
}
private function get($proxy, $url, $get = [], $web_req = false){
$curlproc = curl_init();
if($get !== []){
$get = http_build_query($get);
$url .= "?" . $get;
}
curl_setopt($curlproc, CURLOPT_URL, $url);
curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
// use http2
curl_setopt($curlproc, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
curl_setopt($curlproc, CURLOPT_HTTPHEADER,
["User-Agent: " . config::USER_AGENT,
"Accept: */*",
"Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip, deflate, br, zstd",
"Access-Control-Request-Method: GET",
"Access-Control-Request-Headers: cache-control",
"Referer: https://swisscows.com/",
"Origin: https://swisscows.com",
"DNT: 1",
"Sec-GPC: 1",
"Connection: keep-alive",
"Sec-Fetch-Dest: empty",
"Sec-Fetch-Mode: cors",
"Sec-Fetch-Site: same-site",
"Priority: u=4",
"TE: trailers"]
);
curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
$this->backend->assign_proxy($curlproc, $proxy);
$data = curl_exec($curlproc);
if(curl_errno($curlproc)){
throw new Exception(curl_error($curlproc));
}
curl_close($curlproc);
return $data;
}
public function music($get, $last_attempt = false){
if($get["npt"]){
[$params, $proxy] = $this->backend->get($get["npt"], "music");
$params = json_decode($params, true);
$type = $params["type"];
$search = $params["s"];
$offset = $params["offset"];
}else{
$search = $get["s"];
if(strlen($search) === 0){
throw new Exception("Search term is empty!");
}
$type = $get["type"];
$offset = 0;
$proxy = $this->backend->get_ip();
}
try{
$json =
$this->get(
$proxy,
"https://api.swisscows.com/audio/search/{$type}s",
[
"query" => $search,
"offset" => $offset,
"itemsCount" => 20,
"region" => "en-US"
]
);
}catch(Exception $error){
throw new Exception("Failed to fetch JSON");
}
$json = json_decode($json, true);
if($json === null){
throw new Exception("Failed to decode JSON");
}
$out = [
"status" => "ok",
"npt" => null,
"song" => [],
"playlist" => [],
"album" => [],
"podcast" => [],
"author" => [],
"user" => []
];
if(!isset($json["items"])){
throw new Exception("Swisscows did not return an items object");
}
if($type == "track"){
foreach($json["items"] as $item){
$tags = $item["tags"];
if(!empty($item["genre"])){
$tags[] = $item["genre"];
}
$out["song"][] = [
"title" => $item["title"],
"description" => implode(", ", $tags),
"url" => "/resolver?scraper=sc&target=t{$item["id"]}",
"views" => null,
"author" => [
"name" => null,
"url" => null,
"avatar" => null
],
"thumb" => [
"ratio" => "1:1",
"url" => $item["artworkUrl"]
],
"date" => null,
"duration" => $this->convert_time($item["duration"]),
"stream" => [
"endpoint" => null,
"url" => null
]
];
}
}else{
foreach($json["items"] as $item){
$out["playlist"][] = [
"title" => $item["title"],
"description" => $this->limitstrlen($item["description"]),
"author" => [
"name" => null,
"url" => null,
"avatar" => null
],
"thumb" => [
"ratio" => "1:1",
"url" => $item["artworkUrl"]
],
"date" => null,
"duration" => $this->convert_time($item["duration"]),
"url" => "/resolver?scraper=sc&target=p{$item["id"]}",
];
}
}
//
// get NPT
//
if(
isset($json["nextOffset"]) &&
$json["nextOffset"] !== null
){
$out["npt"] =
$this->backend->store(
json_encode(
[
"type" => $type,
"s" => $search,
"offset" => $json["nextOffset"]
]
),
"music",
$proxy
);
}
return $out;
}
private function limitstrlen($text){
return
explode(
"\n",
wordwrap(
str_replace(
["\n\r", "\r\n", "\n", "\r"],
" ",
$text
),
300,
"\n"
),
2
)[0];
}
private function convert_time($time){
list($hours, $minutes, $seconds) = explode(':', $time);
return
((int)$hours * 3600) +
((int)$minutes * 60) +
(float)$seconds;
}
}
+267
View File
@@ -0,0 +1,267 @@
<?php
class unsplash{
public function __construct(){
include "lib/fuckhtml.php";
$this->fuckhtml = new fuckhtml();
include "lib/backend.php";
$this->backend = new backend("unsplash");
}
public function getfilters($page){
return [
"order_by" => [
"display" => "Order by",
"option" => [
"relevance" => "Relevance",
"latest" => "Newest",
"editorial" => "Curated"
]
],
"orientation" => [
"display" => "Order by",
"option" => [
"any" => "Any orientation",
"landscape" => "Landscape",
"portrait" => "Portrait",
"squarish" => "Square"
]
],
"license" => [
"display" => "License",
"option" => [
"any" => "Any license",
"only" => "Unsplash+",
"none" => "Free"
]
]
];
}
private function get($proxy, $url, $get = [], $referer){
$curlproc = curl_init();
if($get !== []){
$get = http_build_query($get);
$url .= "?" . $get;
}
curl_setopt($curlproc, CURLOPT_URL, $url);
curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
curl_setopt($curlproc, CURLOPT_HTTPHEADER,
["User-Agent: " . config::USER_AGENT,
"Accept: */*",
"Accept-Language: en-US",
"Accept-Encoding: gzip, deflate, br, zstd",
"Referer: {$referer}",
"client-geo-region: global",
"x-client-version: 8999df28be3f138bf2c646df5d656e4dc6970ba0",
"DNT: 1",
"Sec-GPC: 1",
"Connection: keep-alive",
"Sec-Fetch-Dest: empty",
"Sec-Fetch-Mode: cors",
"Sec-Fetch-Site: same-origin",
"Priority: u=0",
"TE: trailers"]
);
curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
$this->backend->assign_proxy($curlproc, $proxy);
$data = curl_exec($curlproc);
if(curl_errno($curlproc)){
throw new Exception(curl_error($curlproc));
}
curl_close($curlproc);
return $data;
}
public function image($get){
if($get["npt"]){
[$filter, $proxy] =
$this->backend->get(
$get["npt"],
"images"
);
$filter = json_decode($filter, true);
}else{
$search = $get["s"];
if(strlen($search) === 0){
throw new Exception("Search term is empty!");
}
$proxy = $this->backend->get_ip();
$filter = [
"page" => 1,
"per_page" => 20,
"query" => $search
];
// add filters
if($get["order_by"] != "relevance"){
$filter["order_by"] = $get["order_by"];
}
if($get["orientation"] != "any"){
$filter["orientation"] = $get["orientation"];
}
if($get["license"] != "any"){
$filter["plus"] = $get["license"];
}
}
$out = [
"status" => "ok",
"npt" => null,
"image" => []
];
// https://unsplash.com/s/photos/shibuya-night?order_by=latest&orientation=landscape&license=free
// https://unsplash.com/s/photos/%3Ctest-%3F!-haha == <test ?! haha
// ?page=1&per_page=20&query=japan
try{
$json =
$this->get(
$proxy,
"https://unsplash.com/napi/search/photos",
$filter,
"https://unsplash.com/s/photos/" . rawurlencode(str_replace(" ", "-", $filter["query"])),
);
}catch(Exception $error){
throw new Exception("Failed to fetch JSON");
}
$json = json_decode($json, true);
if($json === null){
throw new Exception("Could not decode the JSON payload");
}
foreach($json["results"] as $image){
$base = explode("?", $image["urls"]["raw"])[0];
if(
(bool)$image["premium"] ||
(bool)$image["plus"]
){
// when we get "premium" images, give
// 1. highest resolution with watermarks
// 2. highest resolution without watermarks
// (if width of image is above 900px, it has watermarks)
// 3. thumbnail without watermark
$x900 = $this->imgratio((int)$image["width"], (int)$image["height"], 900);
$x500 = $this->imgratio((int)$image["width"], (int)$image["height"], 500);
$source = [
[
"url" => $base,
"width" => (int)$image["width"],
"height" => (int)$image["height"]
],
[
"url" => $base . "?w=900",
"width" => $x900[0],
"height" => $x900[1]
],
[
"url" => $base . "?w=500",
"width" => $x500[0],
"height" => $x500[1]
]
];
}else{
$x500 = $this->imgratio((int)$image["width"], (int)$image["height"], 500);
// image is free as in freedom(tm)
$source = [
[
"url" => $base,
"width" => (int)$image["width"],
"height" => (int)$image["height"]
],
[
"url" => $base . "?w=500",
"width" => $x500[0],
"height" => $x500[1]
]
];
}
$title = [];
$image["description"] = trim($image["description"]);
$image["alt_description"] = trim($image["alt_description"]);
if(!empty($image["description"])){ $title[] = $image["description"]; }
if(!empty($image["alt_description"])){ $title[] = $image["alt_description"]; }
$title = implode(": ", $title);
$out["image"][] = [
"title" => $title,
"source" => $source,
"url" => "https://unsplash.com/photos/" . $image["slug"]
];
}
// next page stuff
if($filter["page"] < (int)$json["total_pages"]){
$filter["page"]++;
$out["npt"] =
$this->backend->store(
json_encode($filter),
"images",
$proxy
);
}
return $out;
}
private function imgratio($width, $height, $max_width){
$ratio = $max_width / $width;
$new_height = floor($height * $ratio);
return [
$max_width,
$new_height
];
}
}
+754
View File
@@ -0,0 +1,754 @@
<?php
class vimeo{
public function __construct(){
include "lib/backend.php";
$this->backend = new backend("vimeo");
include "lib/fuckhtml.php";
$this->fuckhtml = new fuckhtml();
}
public function getfilters($page){
return [
"time" => [
"display" => "Date uploaded", // &filter_uploaded=
"option" => [
"any" => "Any time",
"today" => "Last 24 hours",
"this-week" => "Last 7 days",
"this-month" => "Last 30 days",
"this-year" => "Last 365 days",
]
],
"display" => [
"display" => "Display",
"option" => [
"video" => "Videos",
"ondemand" => "On-Demand ($$)",
"people" => "People",
"channel" => "Channels",
"group" => "Groups"
]
],
"sort" => [
"display" => "Sort by",
"option" => [
"relevance" => "Relevance", // no param
"recent" => "Newest", // &sort=latest&direction=desc
"popular" => "Most popular", // &sort=popularity&direction=desc
"a_z" => "Title, A to Z", // &sort=alphabetical&direction=asc
"z_a" => "Title, Z to A", // &sort=alphabetical&direction=desc
"longest" => "Longest", // &sort=duration&direction=desc
"shortest" => "Shortest", // &sort=duration&direction=asc
]
],
"duration" => [
"display" => "Duration", // &filter_duration=
"option" => [
"any" => "Any duration",
"short" => "Short (less than 4 minutes)",
"medium" => "Medium (4-10 minutes)",
"long" => "Long (over 10 minutes)"
]
],
"resolution" => [
"display" => "Resolution",
"option" => [
"any" => "Any resolution",
"4k" => "4K" // &filter_resolution=4k
]
],
"category" => [
"display" => "Category", // &filter_category=
"option" => [
"any" => "Any category",
"animation" => "Animation",
"comedy" => "Comedy",
"music" => "Music",
"experimental" => "Experimental",
"documentary" => "Documentary",
"identsandanimatedlogos" => "Idents and Animated Logos",
"industry" => "Industry",
"instructionals" => "Instructionals",
"narrative" => "Narrative",
"personal" => "Personal"
]
],
"live" => [
"display" => "Live events",
"option" => [
"any" => "Any",
"yes" => "Live now" // &filter_live=now
]
],
"hdr" => [
"display" => "HDR", // &filter_hdr=
"option" => [
"any" => "Any",
"hdr" => "Any HDR",
"dolby_vision" => "Dolby Vision",
"hdr10" => "HDR10",
"hdr10+" => "HDR10+"
]
],
"vimeo_360" => [
"display" => "Vimeo 360°", // &filter_vimeo_360
"option" => [
"any" => "Any",
"spatial" => "Spatial",
"360" => "360°"
]
],
"price" => [ // &filter_price=
"display" => "Price",
"option" => [
"any" => "Any price",
"free" => "Free",
"paid" => "Paid"
]
],
"collection" => [
"display" => "Vimeo collections",
"option" => [
"any" => "Any collection",
"staff_pick" => "Staff picks" // &filter_staffpicked=true
]
],
"license" => [ // &filter_license=
"display" => "License",
"option" => [
"any" => "Any license",
"by-nc-nd" => "CC BY-NC-ND",
"by" => "CC BY",
"by-nc" => "CC BY-NC",
"by-nc-sa" => "CC BY-NC-SA",
"by-nd" => "CC BY-ND",
"by-sa" => "CC BY-SA",
"cc0" => "CC0"
]
]
];
}
private function get($proxy, $url, $get = [], $jwt = false){
$curlproc = curl_init();
if($get !== []){
$get = http_build_query($get);
$url .= "?" . $get;
}
curl_setopt($curlproc, CURLOPT_URL, $url);
curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
if($jwt === false){
curl_setopt(
$curlproc,
CURLOPT_HTTPHEADER,
["User-Agent: " . config::USER_AGENT,
"Accept: */*",
"Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip, deflate, br, zstd",
"Referer: https://vimeo.com/search",
"X-Requested-With: XMLHttpRequest",
"DNT: 1",
"Sec-GPC: 1",
"Connection: keep-alive",
"Sec-Fetch-Dest: empty",
"Sec-Fetch-Mode: cors",
"Sec-Fetch-Site: same-origin",
"Priority: u=4"]
);
}else{
curl_setopt(
$curlproc,
CURLOPT_HTTPHEADER,
["User-Agent: " . config::USER_AGENT,
"Accept: application/vnd.vimeo.*+json;version=3.3",
"Accept-Language: en",
"Accept-Encoding: gzip, deflate, br, zstd",
"Referer: https://vimeo.com/",
"Content-Type: application/json",
"Authorization: jwt $jwt",
"Vimeo-Page: /search/[[...slug]]",
"Origin: https://vimeo.com",
"DNT: 1",
"Sec-GPC: 1",
"Connection: keep-alive",
"Sec-Fetch-Dest: empty",
"Sec-Fetch-Mode: cors",
"Sec-Fetch-Site: same-site",
"Priority: u=4"]
);
}
curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
$this->backend->assign_proxy($curlproc, $proxy);
$data = curl_exec($curlproc);
if(curl_errno($curlproc)){
throw new Exception(curl_error($curlproc));
}
curl_close($curlproc);
return $data;
}
public function video($get){
// parse shit
if($get["npt"]){
[$npt, $proxy] =
$this->backend
->get(
$get["npt"],
"videos"
);
$npt = json_decode($npt, true);
$pagetype = $npt["pagetype"];
$npt = $npt["npt"];
$jwt = $this->get_jwt($proxy);
try{
$json =
$this->get(
$proxy,
"https://api.vimeo.com" . $npt,
[],
$jwt
);
}catch(Exception $error){
throw new Exception("Failed to fetch JSON");
}
}else{
$proxy = null;
$jwt = $this->get_jwt($proxy); // this gives us a proxy by reference
// parse filters
$npt = [
"query" => $get["s"],
"page" => 1,
"per_page" => 24,
"facets" => "type"
];
switch($get["display"]){
case "video":
$npt["filter_type"] = "clip";
$npt["fields"] = "clip.name,stats.plays,clip.pictures,clip.user.name,clip.user.link,clip.user.pictures.sizes,clip.uri,clip.stats.plays,clip.duration,clip.created_time,clip.link,clip.description";
break;
case "ondemand":
$npt["filter_type"] = "ondemand";
$npt["sizes"] = "296x744";
$npt["fields"] = "ondemand.link,ondemand.name,ondemand.pictures.sizes,ondemand.metadata.interactions.buy,ondemand.metadata.interactions.rent,ondemand.uri";
break;
case "people":
$npt["filter_type"] = "people";
$npt["fetch_user_profile"] = "1";
$npt["fields"] = "people.name,people.location_details.formatted_address,people.metadata.public_videos.total,people.pictures.sizes,people.link,people.metadata.connections.followers.total,people.skills.name,people.skills.uri,people.background_video,people.uri";
break;
case "channel":
$npt["filter_type"] = "channel";
$npt["fields"] = "channel.name,channel.metadata.connections.users.total,channel.metadata.connections.videos.total,channel.pictures.sizes,channel.link,channel.uri";
break;
case "group":
$npt["filter_type"] = "group";
$npt["fields"] = "group.name,group.metadata.connections.users.total,group.metadata.connections.videos.total,group.pictures.sizes,group.link,group.uri";
break;
}
// only apply filters if we're searching for videos
if($get["display"] == "video"){
switch($get["sort"]){
case "relevance": break; // do nothing
case "recent":
$npt["sort"] = "latest";
$npt["direction"] = "desc";
break;
case "popular":
$npt["sort"] = "popularity";
$npt["direction"] = "desc";
break;
case "a_z":
$npt["sort"] = "alphabetical";
$npt["direction"] = "asc";
break;
case "z_a":
$npt["sort"] = "alphabetical";
$npt["direction"] = "desc";
break;
case "longest":
$npt["sort"] = "duration";
$npt["direction"] = "desc";
break;
case "shortest":
$npt["sort"] = "duration";
$npt["direction"] = "asc";
break;
}
if($get["time"] != "any"){
$npt["filter_uploaded"] = $get["time"];
}
if($get["duration"] != "any"){
$npt["filter_duration"] = $get["duration"];
}
if($get["resolution"] != "any"){
$npt["filter_resolution"] = $get["resolution"];
}
if($get["category"] != "any"){
$npt["filter_category"] = $get["category"];
}
if($get["live"] != "any"){
$npt["filter_live"] = "now";
}
if($get["hdr"] != "any"){
$npt["filter_hdr"] = $get["hdr"];
}
if($get["vimeo_360"] != "any"){
$npt["filter_vimeo_360"] = $get["vimeo_360"];
}
if($get["price"] != "any"){
$npt["filter_price"] = $get["price"];
}
if($get["collection"] == "staff_pick"){
$npt["filter_staffpicked"] = "true";
}
if($get["license"] != "any"){
$npt["filter_license"] = $get["license"];
}
}
$pagetype = $npt["filter_type"];
try{
$json =
$this->get(
$proxy,
"https://api.vimeo.com/search",
$npt,
$jwt
);
}catch(Exception $error){
throw new Exception("Failed to fetch JSON");
}
}
$json = json_decode($json, true);
if($json === null){
throw new Exception("Failed to parse JSON");
}
$out = [
"status" => "ok",
"npt" => null,
"video" => [],
"author" => [],
"livestream" => [],
"playlist" => [],
"reel" => []
];
if(isset($json["error"])){
$error = $json["error"];
if(isset($json["developer_message"])){
$error .= " ({$json["developer_message"]})";
}
throw new Exception("Vimeo returned an error: " . $error);
}
if(!isset($json["data"])){
throw new Exception("Vimeo did not return a data object");
}
switch($pagetype){
case "clip":
foreach($json["data"] as $video){
$video = $video["clip"];
if(isset($video["user"]["pictures"]["sizes"])){
$avatar = $video["user"]["pictures"]["sizes"][count($video["user"]["pictures"]["sizes"]) - 1]["link"];
}else{
$avatar = null;
}
$out["video"][] = [
"title" => $video["name"],
"description" =>
$this->limitstrlen(
$video["description"]
),
"author" => [
"name" => $video["user"]["name"],
"url" => $video["user"]["link"],
"avatar" => $avatar
],
"date" => strtotime($video["created_time"]),
"duration" => (int)$video["duration"],
"views" => (int)$video["stats"]["plays"],
"thumb" => [
"ratio" => "16:9",
"url" => $video["pictures"]["base_link"]
],
"url" => $video["link"]
];
}
break;
case "ondemand":
foreach($json["data"] as $video){
$video = $video["ondemand"];
$description = [];
if(isset($video["metadata"]["interactions"]["rent"]["display_price"])){
$description[] = "Rent for " . $video["metadata"]["interactions"]["rent"]["display_price"];
}
if(isset($video["metadata"]["interactions"]["buy"]["display_price"])){
$description[] = "Buy for " . $video["metadata"]["interactions"]["buy"]["display_price"];
}
$description = implode(", ", $description);
$out["video"][] = [
"title" => $video["name"],
"description" => $description,
"author" => [
"name" => null,
"url" => null,
"avatar" => null
],
"date" => null,
"duration" => null,
"views" => null,
"thumb" => [
"ratio" => "9:16",
"url" => $video["pictures"]["sizes"][0]["link"]
],
"url" => $video["link"]
];
}
break;
case "people":
foreach($json["data"] as $user){
$user = $user["people"];
if(
isset($user["pictures"]["sizes"]) &&
count($user["pictures"]["sizes"]) !== 0
){
$thumb = [
"ratio" => "1:1",
"url" => $user["pictures"]["sizes"][count($user["pictures"]["sizes"]) - 1]["link"]
];
}else{
$thumb = [
"ratio" => null,
"url" => null
];
}
$out["author"][] = [
"title" => $user["name"],
"followers" => (int)$user["metadata"]["connections"]["followers"]["total"],
"description" => $user["metadata"]["public_videos"]["total"] . " videos.",
"thumb" => $thumb,
"url" => $user["link"]
];
}
break;
case "channel":
case "group":
foreach($json["data"] as $channel){
$channel = $channel[$npt["filter_type"]];
if(
isset($channel["pictures"]["sizes"]) &&
count($channel["pictures"]["sizes"]) !== 0
){
$thumb = [
"ratio" => "16:9",
"url" => $channel["pictures"]["sizes"][count($channel["pictures"]["sizes"]) - 1]["link"]
];
}else{
$thumb = [
"ratio" => null,
"url" => null
];
}
$out["author"][] = [
"title" => $channel["name"],
"followers" => (int)$channel["metadata"]["connections"]["users"]["total"],
"description" => $channel["metadata"]["connections"]["videos"]["total"] . " videos.",
"thumb" => $thumb,
"url" => $channel["link"]
];
}
break;
}
//
// get next page
//
if(
isset($json["paging"]["next"]) &&
is_string($json["paging"]["next"])
){
$out["npt"] =
$this->backend
->store(
json_encode([
"npt" => $json["paging"]["next"],
"pagetype" => $pagetype
]),
"videos",
$proxy
);
}
return $out;
}
private function get_jwt(&$proxy){
//
// get jwt token
// it's probably safe to cache this across proxies, cause the jwt doesnt contain an userID
// only an appID, whatever shit that is
// we can only cache it for 5 minutes though, otherwise vimeo cries about it
//
if($proxy === null){
$proxy = $this->backend->get_ip();
}
$jwt = apcu_fetch("vimeo_jwt");
if($jwt === false){
/*
$html =
$this->get(
$proxy,
"https://vimeo.com/search",
[],
false
);
$this->fuckhtml->load($html);
$captcha =
$this->fuckhtml
->getElementsByTagName(
"title"
);
if(
count($captcha) !== 0 &&
$this->fuckhtml
->getTextContent(
$captcha[0]
) == "Vimeo / CAPTCHA Challenge"
){
throw new Exception("Vimeo returned a Captcha");
}
$html =
explode(
'<script id="viewer-bootstrap" type="application/json">',
$html,
2
);
if(count($html) !== 2){
throw new Exception("Failed to find JWT json");
}
$jwt =
json_decode(
$this->fuckhtml
->extract_json(
$html[1]
),
true
);
if($jwt === null){
throw new Exception("Failed to decode JWT json");
}
if(!isset($jwt["jwt"])){
throw new Exception("Failed to grep JWT");
}
$jwt = $jwt["jwt"];
*/
try{
$json =
$this->get(
$proxy,
"https://vimeo.com/_next/jwt",
[],
false
);
}catch(Exception $error){
throw new Exception("Failed to fetch JWT token");
}
$this->fuckhtml->load($json);
$captcha =
$this->fuckhtml
->getElementsByTagName(
"title"
);
if(
count($captcha) !== 0 &&
$this->fuckhtml
->getTextContent(
$captcha[0]
) == "Vimeo / CAPTCHA Challenge"
){
throw new Exception("Vimeo returned a Captcha");
}
$json = json_decode($json, true);
if($json === null){
throw new Exception("The JWT object could not be decoded");
}
if(!isset($json["token"])){
throw new Exception("Vimeo did not return a JWT");
}
$jwt = $json["token"];
apcu_store("vimeo_jwt", $jwt, 300);
}
return $jwt;
}
private function titledots($title){
$substr = substr($title, -3);
if(
$substr == "..." ||
$substr == "…"
){
return trim(substr($title, 0, -3), " \n\r\t\v\x00\0\x0B\xc2\xa0");
}
return trim($title, " \n\r\t\v\x00\0\x0B\xc2\xa0");
}
private function limitstrlen($text){
return
explode(
"\n",
wordwrap(
str_replace(
["\n\r", "\r\n", "\n", "\r"],
" ",
$text
),
300,
"\n"
),
2
)[0];
}
}
+256
View File
@@ -0,0 +1,256 @@
<?php
class vsco{
public function __construct(){
include "lib/backend.php";
$this->backend = new backend("vsco");
}
public function getfilters($page){
return [];
}
private function get($proxy, $url, $get = [], $bearer = null){
$curlproc = curl_init();
if($get !== []){
$get_tmp = http_build_query($get);
$url .= "?" . $get_tmp;
}
curl_setopt($curlproc, CURLOPT_URL, $url);
curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
if($bearer === null){
curl_setopt($curlproc, CURLOPT_HTTPHEADER,
["User-Agent: " . config::USER_AGENT,
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language: en-US,en;q=0.5",
"Accept-Encoding: gzip, deflate, br, zstd",
"DNT: 1",
"Sec-GPC: 1",
"Connection: keep-alive",
"Upgrade-Insecure-Requests: 1",
"Sec-Fetch-Dest: document",
"Sec-Fetch-Mode: navigate",
"Sec-Fetch-Site: none",
"Sec-Fetch-User: ?1",
"Priority: u=0, i"]
);
}else{
curl_setopt($curlproc, CURLOPT_HTTPHEADER,
["User-Agent: " . config::USER_AGENT,
"Accept: */*",
"Accept-Language: en-US",
"Accept-Encoding: gzip",
"Referer: https://vsco.co/search/images/" . urlencode($get["query"]),
"authorization: Bearer " . $bearer,
"content-type: application/json",
"x-client-build: 1",
"x-client-platform: web",
"DNT: 1",
"Sec-GPC: 1",
"Connection: keep-alive",
"Sec-Fetch-Dest: empty",
"Sec-Fetch-Mode: cors",
"Sec-Fetch-Site: same-origin",
"Priority: u=0",
"TE: trailers"]
);
}
curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($curlproc, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($curlproc, CURLOPT_TIMEOUT, 30);
// http1 bypass?!?!?!
curl_setopt($curlproc, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
$this->backend->assign_proxy($curlproc, $proxy);
$data = curl_exec($curlproc);
if(curl_errno($curlproc)){
throw new Exception(curl_error($curlproc));
}
curl_close($curlproc);
return $data;
}
public function image($get){
if($get["npt"]){
[$data, $proxy] =
$this->backend->get(
$get["npt"], "images"
);
$data = json_decode($data, true);
}else{
$search = $get["s"];
if(strlen($search) === 0){
throw new Exception("Search term is empty!");
}
$proxy = $this->backend->get_ip();
// get bearer token
try{
$html =
$this->get(
$proxy,
"https://vsco.co/feed"
);
}catch(Exception $error){
throw new Exception("Failed to fetch feed page");
}
preg_match(
'/"tkn":"([A-z0-9]+)"/',
$html,
$bearer
);
if(!isset($bearer[1])){
throw new Exception("Failed to grep bearer token");
}
$data = [
"pagination" => [
"query" => $search,
"page" => 0,
"size" => 100
],
"bearer" => $bearer[1]
];
}
try{
$json =
$this->get(
$proxy,
"https://vsco.co/api/2.0/search/images",
$data["pagination"],
$data["bearer"]
);
}catch(Exception $error){
throw new Exception("Failed to fetch JSON");
}
$json = json_decode($json, true);
if($json === null){
throw new Exception("Failed to decode JSON");
}
$out = [
"status" => "ok",
"npt" => null,
"image" => []
];
if(!isset($json["results"])){
throw new Exception("Failed to access results object");
}
foreach($json["results"] as $image){
$image_domain = parse_url("https://" . $image["responsive_url"], PHP_URL_HOST);
$thumbnail = explode($image_domain, $image["responsive_url"], 2)[1];
if(substr($thumbnail, 0, 3) != "/1/"){
$thumbnail =
preg_replace(
'/^\/[^\/]+/',
"",
$thumbnail
);
}
$thumbnail = "https://img.vsco.co/cdn-cgi/image/width=480,height=360" . $thumbnail;
$size =
$this->image_ratio(
(int)$image["dimensions"]["width"],
(int)$image["dimensions"]["height"]
);
$out["image"][] = [
"title" => $image["description"],
"source" => [
[
"url" => "https://" . $image["responsive_url"],
"width" => (int)$image["dimensions"]["width"],
"height" => (int)$image["dimensions"]["height"]
],
[
"url" => $thumbnail,
"width" => $size[0],
"height" => $size[1]
]
],
"url" => "https://" . $image["grid"]["domain"] . "/media/" . $image["imageId"]
];
}
// get NPT
$max_page = ceil($json["total"] / 100);
$data["pagination"]["page"]++;
if($max_page > $data["pagination"]["page"]){
$out["npt"] =
$this->backend->store(
json_encode($data),
"images",
$proxy
);
}
return $out;
}
private function image_ratio($width, $height){
$ratio = [
480 / $width,
360 / $height
];
if($ratio[0] < $ratio[1]){
$ratio = $ratio[0];
}else{
$ratio = $ratio[1];
}
return [
floor($width * $ratio),
floor($height * $ratio)
];
}
}
File diff suppressed because it is too large. Load diff
+210 -273
View File
@@ -14,7 +14,7 @@ class yandex{
// backend included in the scraper functions // backend included in the scraper functions
} }
private function get($proxy, $url, $get = [], $nsfw){ private function get($proxy, $url, $get = [], $nsfw, $get_cookie = 1){
$curlproc = curl_init(); $curlproc = curl_init();
@@ -25,19 +25,55 @@ class yandex{
curl_setopt($curlproc, CURLOPT_URL, $url); curl_setopt($curlproc, CURLOPT_URL, $url);
// extract "i" cookie
if($get_cookie === 0){
$cookies_tmp = [];
curl_setopt($curlproc, CURLOPT_HEADERFUNCTION, function($curlproc, $header) use (&$cookies_tmp){
$length = strlen($header);
$header = explode(":", $header, 2);
if(trim(strtolower($header[0])) == "set-cookie"){
$cookie_tmp = explode("=", trim($header[1]), 2);
$cookies_tmp[trim($cookie_tmp[0])] =
explode(";", $cookie_tmp[1], 2)[0];
}
return $length;
});
}
switch($nsfw){ switch($nsfw){
case "yes": $nsfw = "0"; break; case "yes": $nsfw = "0"; break;
case "maybe": $nsfw = "1"; break; case "maybe": $nsfw = "1"; break;
case "no": $nsfw = "2"; break; case "no": $nsfw = "2"; break;
} }
switch($get_cookie){
case 0:
$cookie = "";
break;
case 1:
$cookie = "Cookie: yp=" . (time() - 4000033) . ".szm.1:1920x1080:876x1000#" . time() . ".sp.family:" . $nsfw;
break;
default:
$cookie = "Cookie: i=" . $get_cookie;
}
$headers = $headers =
["User-Agent: " . config::USER_AGENT, ["User-Agent: " . config::USER_AGENT,
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8", "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Encoding: gzip", "Accept-Encoding: gzip",
"Accept-Language: en-US,en;q=0.5", "Accept-Language: en-US,en;q=0.5",
"DNT: 1", "DNT: 1",
"Cookie: yp=1716337604.sp.family%3A{$nsfw}#1685406411.szm.1:1920x1080:1920x999", $cookie,
"Referer: https://yandex.com/images/search", "Referer: https://yandex.com/images/search",
"Connection: keep-alive", "Connection: keep-alive",
"Upgrade-Insecure-Requests: 1", "Upgrade-Insecure-Requests: 1",
@@ -59,6 +95,17 @@ class yandex{
$data = curl_exec($curlproc); $data = curl_exec($curlproc);
if($get_cookie === 0){
if(isset($cookies_tmp["i"])){
return $cookies_tmp["i"];
}else{
throw new Exception("Failed to get Yandex clearance cookie");
}
}
if(curl_errno($curlproc)){ if(curl_errno($curlproc)){
throw new Exception(curl_error($curlproc)); throw new Exception(curl_error($curlproc));
@@ -217,6 +264,23 @@ class yandex{
// https://yandex.com/search/site/?text=minecraft&web=1&frame=1&v=2.0&searchid=3131712 // https://yandex.com/search/site/?text=minecraft&web=1&frame=1&v=2.0&searchid=3131712
// &within=777&from_day=26&from_month=8&from_year=2023&to_day=26&to_month=8&to_year=2023 // &within=777&from_day=26&from_month=8&from_year=2023&to_day=26&to_month=8&to_year=2023
// get clearance cookie
if(($cookie = apcu_fetch("yandexweb_cookie")) === false){
$proxy = $this->backend->get_ip();
$cookie =
$this->get(
$proxy,
"https://yandex.ru/support2/smart-captcha/ru/",
[],
false,
0
);
apcu_store("yandexweb_cookie", $cookie);
}
if($get["npt"]){ if($get["npt"]){
[$npt, $proxy] = $this->backend->get($get["npt"], "web"); [$npt, $proxy] = $this->backend->get($get["npt"], "web");
@@ -226,7 +290,8 @@ class yandex{
$proxy, $proxy,
"https://yandex.com" . $npt, "https://yandex.com" . $npt,
[], [],
"yes" "yes",
$cookie
); );
}else{ }else{
@@ -236,7 +301,7 @@ class yandex{
throw new Exception("Search term is empty!"); throw new Exception("Search term is empty!");
} }
$proxy = $this->backend->get_ip(); $proxy = !isset($proxy) ? $this->backend->get_ip() : $proxy;
$lang = $get["lang"]; $lang = $get["lang"];
$older = $get["older"]; $older = $get["older"];
$newer = $get["newer"]; $newer = $get["newer"];
@@ -283,7 +348,8 @@ class yandex{
$proxy, $proxy,
"https://yandex.com/search/site/", "https://yandex.com/search/site/",
$params, $params,
"yes" "yes",
$cookie
); );
}catch(Exception $error){ }catch(Exception $error){
@@ -314,6 +380,19 @@ class yandex{
$this->fuckhtml->load($html); $this->fuckhtml->load($html);
// Scrape page blocked error
$title =
$this->fuckhtml
->getElementsByTagName("title");
if(
count($title) !== 0 &&
$title[0]["innerHTML"] == "403"
){
throw new Exception("Yandex blocked this proxy or 4get instance.");
}
// get nextpage // get nextpage
$npt = $npt =
$this->fuckhtml $this->fuckhtml
@@ -668,7 +747,6 @@ class yandex{
foreach($json["blocks"] as $block){ foreach($json["blocks"] as $block){
$html .= $block["html"]; $html .= $block["html"];
// get next page // get next page
if( if(
isset($block["params"]["nextPageUrl"]) && isset($block["params"]["nextPageUrl"]) &&
@@ -790,123 +868,71 @@ class yandex{
if($get["npt"]){ if($get["npt"]){
[$params, $proxy] = [$get, $proxy] =
$this->backend->get( $this->backend->get(
$get["npt"], $get["npt"],
"video" "video"
); );
$params = json_decode($params, true); $get = json_decode($get, true);
$nsfw = $params["nsfw"];
unset($params["nsfw"]);
}else{ }else{
$search = $get["s"]; if(strlen($get["s"]) === 0){
if(strlen($search) === 0){
throw new Exception("Search term is empty!"); throw new Exception("Search term is empty!");
} }
$proxy = $this->backend->get_ip(); $proxy = $this->backend->get_ip();
$nsfw = $get["nsfw"]; }
$time = $get["time"];
$duration = $get["duration"];
// https://yandex.com/video/search?text=skycamefalling&from=tabbar&format=json&ncrnd=7271&p=0&parent-reqid=&request={%22blocks%22%3A[{%22block%22%3A%22video-app%22%2C%22params%22%3A{}}]}&serpid=1777751040971457-16832445014469941403-balancer-l7leveler-kubr-yp-klg-151-BAL&yu=3091577281773194415&tmpl_version=releases-frontend-video-v1.1816.0__3bdc24e10a8a138a1194877428e220a3ca0dbc5a
// https://yandex.com/video/search // https://yandex.com/video/search
// ?tmpl_version=releases/frontend/video/v1.1168.0#8d942de0f4ebc4eb6b8f3c24ffbd1f8dbc5bbe63 // ?text=skycamefalling
// &format=json
// &request=
// {
// "blocks":[
// {"block":"extra-content","params":{},"version":2},
// {"block":"i-global__params:ajax","params":{},"version":2},
// {"block":"search2:ajax","params":{},"version":2},
// {"block":"vital-incut","params":{},"version":2},
// {"block":"content_type_search","params":{},"version":2},
// {"block":"serp-controller","params":{},"version":2},
// {"block":"cookies_ajax","params":{},"version":2}
// ],
// "metadata":{
// "bundles":{"lb":"^G]!q<X120"},
// "assets":{"las":"react-with-dom=1;185.0=1;73.0=1;145.0=1;5a502a.0=1;32c342.0=1;b84ac8.0=1"},
// "extraContent":{"names":["i-react-ajax-adapter"]}
// }
// }
// &yu=4861394161661655015
// &from=tabbar // &from=tabbar
// &reqid=1693106278500184-6825210746979814879-balancer-l7leveler-kubr-yp-sas-7-BAL-4237 // &format=json
// &suggest_reqid=486139416166165501562797413447032 // &ncrnd=7271
// &text=minecraft // &p=0
// &parent-reqid=
// &request={%22blocks%22%3A[{%22block%22%3A%22video-app%22%2C%22params%22%3A{}}]} {"blocks":[{"block":"video-app","params":{}}]}
// &serpid=1777751040971457-16832445014469941403-balancer-l7leveler-kubr-yp-klg-151-BAL
// &yu=3091577281773194415
// &tmpl_version=releases-frontend-video-v1.1816.0__3bdc24e10a8a138a1194877428e220a3ca0dbc5a
$params = [ $params = [
"tmpl_version" => "releases/frontend/video/v1.1168.0#8d942de0f4ebc4eb6b8f3c24ffbd1f8dbc5bbe63", "text" => $get["s"],
"from" => "tabbar",
"format" => "json", "format" => "json",
"request" => json_encode([ "ncrnd" => 7271,
"p" => 0,
"parent-reqid" => "",
"request" => json_encode((object)[
"blocks" => [ "blocks" => [
(object)[ (object)[
"block" => "extra-content", "block" => "video-app",
"params" => (object)[], "params" => (object)[]
"version" => 2
],
(object)[
"block" => "i-global__params:ajax",
"params" => (object)[],
"version" => 2
],
(object)[
"block" => "search2:ajax",
"params" => (object)[],
"version" => 2
],
(object)[
"block" => "vital-incut",
"params" => (object)[],
"version" => 2
],
(object)[
"block" => "content_type_search",
"params" => (object)[],
"version" => 2
],
(object)[
"block" => "serp-controller",
"params" => (object)[],
"version" => 2
],
(object)[
"block" => "cookies_ajax",
"params" => (object)[],
"version" => 2
]
],
"metadata" => (object)[
"bundles" => (object)[
"lb" => "^G]!q<X120"
],
"assets" => (object)[
"las" => "react-with-dom=1;185.0=1;73.0=1;145.0=1;5a502a.0=1;32c342.0=1;b84ac8.0=1"
],
"extraContent" => (object)[
"names" => [
"i-react-ajax-adapter"
]
] ]
] ]
]), ]),
"text" => $search "serpid" => "1777751040971457-16832445014469941403-balancer-l7leveler-kubr-yp-klg-151-BAL",
"yu" => 3091577281773194415,
"tmpl_version" => "releases-frontend-video-v1.1816.0__3bdc24e10a8a138a1194877428e220a3ca0dbc5a"
]; ];
if($duration != "any"){ if(isset($get["p"])){
$params["duration"] = $duration; $params["p"] = $get["p"];
} }
if($time != "any"){ if($get["duration"] != "any"){
$params["within"] = $time; $params["duration"] = $get["duration"];
} }
if($get["time"] != "any"){
$params["within"] = $get["time"];
} }
/* /*
$handle = fopen("scraper/yandex-video.json", "r"); $handle = fopen("scraper/yandex-video.json", "r");
$json = fread($handle, filesize("scraper/yandex-video.json")); $json = fread($handle, filesize("scraper/yandex-video.json"));
@@ -918,7 +944,7 @@ class yandex{
$proxy, $proxy,
"https://yandex.com/video/search", "https://yandex.com/video/search",
$params, $params,
$nsfw, $get["nsfw"],
"yandex_v" "yandex_v"
); );
}catch(Exception $error){ }catch(Exception $error){
@@ -933,7 +959,7 @@ class yandex{
throw new Exception("Could not parse JSON"); throw new Exception("Could not parse JSON");
} }
if(!isset($json["blocks"])){ if(!isset($json["results"]["clips"]["items"])){
throw new Exception("Yandex blocked this 4get instance. Please try again in 7~ minutes."); throw new Exception("Yandex blocked this 4get instance. Please try again in 7~ minutes.");
} }
@@ -948,209 +974,120 @@ class yandex{
"reel" => [] "reel" => []
]; ];
$html = null; foreach($json["results"]["clips"]["items"] as $k => $data){
foreach($json["blocks"] as $block){
if(isset($block["html"])){ if(isset($data["preview"]["posterSrc"])){
$html .= $block["html"]; $poster = $data["preview"]["posterSrc"];
}
if(
preg_match(
'/^\/\//',
$data["preview"]["posterSrc"]
)
){
$poster = "https:" . $poster;
} }
$this->fuckhtml->load($html); $thumb = [
"ratio" => "16:9",
"url" => $poster
];
}else{
$div = $thumb = [
$this->fuckhtml "ratio" => null,
->getElementsByTagName("div"); "url" => null
];
}
/* $out["video"][] = [
Get nextpage "title" => $data["relatedParams"]["text"],
*/ "description" => $this->titledots($data["description"]),
$npt = "author" => [
$this->fuckhtml "name" =>
->getElementsByClassName( isset($json["results"]["clips"]["dups"][$k]["host"]["secondPart"]["name"]) ?
"more more_direction_next i-bem", $json["results"]["clips"]["dups"][$k]["host"]["secondPart"]["name"] : null,
$div "url" =>
); isset($json["results"]["clips"]["dups"][$k]["host"]["secondPart"]["origUrl"]) ?
$json["results"]["clips"]["dups"][$k]["host"]["secondPart"]["origUrl"] : null,
"avatar" => null
],
"date" =>
isset($json["results"]["clips"]["dups"][$k]["date"]) ?
strtotime($json["results"]["clips"]["dups"][$k]["date"]) : null,
"duration" =>
isset($json["results"]["clips"]["dups"][$k]["duration"]["value"]) ?
(int)$json["results"]["clips"]["dups"][$k]["duration"]["value"] : null,
"views" =>
isset($json["results"]["clips"]["dups"][$k]["views"]["text"]) ?
$this->parseviews($json["results"]["clips"]["dups"][$k]["views"]["text"]) : null,
"thumb" => $thumb,
"url" =>
preg_replace(
'/^http:\/\//',
"https://",
$data["relatedParams"]["related_url"]
)
];
}
if(count($npt) !== 0){ // get npt
if($json["results"]["search"]["hasNextPage"]){
$get["p"] = (int)$json["results"]["search"]["currentPage"] + 1;
$params["p"] = "1";
$params["nsfw"] = $nsfw;
$out["npt"] = $out["npt"] =
$this->backend->store( $this->backend->store(
json_encode($params), json_encode($get),
"video", "video",
$proxy $proxy
); );
} }
$items =
$this->fuckhtml
->getElementsByClassName(
"serp-item",
$div
);
foreach($items as $item){
$data =
json_decode(
$this->fuckhtml
->getTextContent(
$item["attributes"]["data-video"]
),
true
);
$this->fuckhtml->load($item);
$thumb =
$this->fuckhtml
->getElementsByClassName(
"thumb-image__image",
"img"
);
$c = 1;
if(count($thumb) === 0){
$thumb = [
"url" => null,
"ratio" => null
];
}else{
$thumb = [
"url" =>
str_replace(
"//",
"https://",
$this->fuckhtml
->getTextContent(
$thumb
[0]
["attributes"]
["src"]
),
$c
),
"ratio" => "16:9"
];
}
$smallinfos =
$this->fuckhtml
->getElementsByClassName(
"serp-item__sitelinks-item",
"div"
);
$date = null;
$views = null;
$first = true;
foreach($smallinfos as $info){
if($first){
$first = false;
continue;
}
$info =
$this->fuckhtml
->getTextContent(
$info
);
if($temp_date = strtotime($info)){
$date = $temp_date;
}else{
$views = $this->parseviews($info);
}
}
$description =
$this->fuckhtml
->getElementsByClassName(
"serp-item__text serp-item__text_visibleText_always",
"div"
);
if(count($description) === 0){
$description = null;
}else{
$description =
$this->titledots(
$this->fuckhtml
->getTextContent(
$description[0]
)
);
}
$out["video"][] = [
"title" =>
$this->fuckhtml
->getTextContent(
$this->titledots(
$data["title"]
)
),
"description" => $description,
"author" => [
"name" => null,
"url" => null,
"avatar" => null
],
"date" => $date,
"duration" =>
(int)$data
["counters"]
["toHostingLoaded"]
["stredParams"]
["duration"],
"views" => $views,
"thumb" => $thumb,
"url" =>
str_replace(
"http://",
"https://",
$this->fuckhtml
->getTextContent(
$data["counters"]
["toHostingLoaded"]
["postfix"]
["href"]
),
$c
)
];
}
return $out; return $out;
} }
private function parseviews($text){ private function parseviews($number){
$text = explode(" ", $text); // decimal should always be 1 number long
$number = explode(" ", $number, 2);
$number = $number[0];
$num = (float)$text[0]; $unit = strtolower($number[strlen($number) - 1]);
$mod = $text[1];
switch($mod){ $tmp = explode(".", $number, 2);
$number = (int)$number;
case "bln.": $num = $num * 1000000000; break; if(count($tmp) === 2){
case "mln.": $num = $num * 1000000; break;
case "thsd.": $num = $num * 1000; break; $decimal = (int)$tmp[1];
}else{
$decimal = 0;
} }
return $num; switch($unit){
case "k":
$exponant = 1000;
break;
case "m":
$exponant = 1000000;
break;
case "b";
$exponant = 1000000000;
break;
default:
$exponant = 1;
break;
}
return ($number * $exponant) + ($decimal * ($exponant / 10));
} }
private function titledots($title){ private function titledots($title){
+303 -462
View File
@@ -14,234 +14,209 @@ class yep{
public function getfilters($page){ public function getfilters($page){
return [ return [
"country" => [ "lang" => [
"display" => "Country", "display" => "Language",
"option" => [ "option" => [
"all" => "All regions", "any" => "Any language",
"af" => "Afghanistan", "aa" => "Afar",
"al" => "Albania", "ab" => "Abkhazian",
"dz" => "Algeria", "ae" => "Avestan",
"as" => "American Samoa", "af" => "Afrikaans",
"ad" => "Andorra", "ak" => "Akan",
"ao" => "Angola", "am" => "Amharic",
"ai" => "Anguilla", "an" => "Aragonese",
"ag" => "Antigua and Barbuda", "ar" => "Arabic",
"ar" => "Argentina", "as" => "Assamese",
"am" => "Armenia", "av" => "Avaric",
"aw" => "Aruba", "ay" => "Aymara",
"au" => "Australia", "az" => "Azerbaijani",
"at" => "Austria", "ba" => "Bashkir",
"az" => "Azerbaijan", "be" => "Belarusian",
"bs" => "Bahamas", "bg" => "Bulgarian",
"bh" => "Bahrain", "bh" => "Bihari",
"bd" => "Bangladesh", "bi" => "Bislama",
"bb" => "Barbados", "bm" => "Bambara",
"by" => "Belarus", "bn" => "Bengali",
"be" => "Belgium", "bo" => "Tibetan",
"bz" => "Belize", "br" => "Breton",
"bj" => "Benin", "bs" => "Bosnian",
"bt" => "Bhutan", "ca" => "Catalan",
"bo" => "Bolivia", "ce" => "Chechen",
"ba" => "Bosnia and Herzegovina", "ch" => "Chamorro",
"bw" => "Botswana", "co" => "Corsican",
"br" => "Brazil", "cr" => "Cree",
"bn" => "Brunei Darussalam", "cs" => "Czech",
"bg" => "Bulgaria", "cu" => "Church Slavic",
"bf" => "Burkina Faso", "cv" => "Chuvash",
"bi" => "Burundi", "cy" => "Welsh",
"cv" => "Cabo Verde", "da" => "Danish",
"kh" => "Cambodia", "de" => "German",
"cm" => "Cameroon", "dv" => "Divehi",
"ca" => "Canada", "dz" => "Dzongkha",
"ky" => "Cayman Islands", "ee" => "Ewe",
"cf" => "Central African Republic", "el" => "Greek",
"td" => "Chad", "en" => "English",
"cl" => "Chile", "eo" => "Esperanto",
"cn" => "China", "es" => "Spanish",
"co" => "Colombia", "et" => "Estonian",
"cg" => "Congo", "eu" => "Basque",
"cd" => "Congo, Democratic Republic", "fa" => "Persian",
"ck" => "Cook Islands", "ff" => "Fulah",
"cr" => "Costa Rica", "fi" => "Finnish",
"hr" => "Croatia", "fj" => "Fijian",
"cu" => "Cuba", "fo" => "Faroese",
"cy" => "Cyprus", "fr" => "French",
"cz" => "Czechia", "fy" => "Western Frisian",
"ci" => "Côte d'Ivoire", "ga" => "Irish",
"dk" => "Denmark", "gd" => "Scottish Gaelic",
"dj" => "Djibouti", "gl" => "Galician",
"dm" => "Dominica", "gn" => "Guarani",
"do" => "Dominican Republic", "gu" => "Gujarati",
"ec" => "Ecuador", "gv" => "Manx",
"eg" => "Egypt", "ha" => "Hausa",
"sv" => "El Salvador", "he" => "Hebrew",
"gq" => "Equatorial Guinea", "hi" => "Hindi",
"ee" => "Estonia", "ho" => "Hiri Motu",
"et" => "Ethiopia", "hr" => "Croatian",
"fo" => "Faroe Islands", "ht" => "Haitian",
"fj" => "Fiji", "hu" => "Hungarian",
"fi" => "Finland", "hy" => "Armenian",
"fr" => "France", "hz" => "Herero",
"gf" => "French Guiana", "ia" => "Interlingua",
"pf" => "French Polynesia", "id" => "Indonesian",
"ga" => "Gabon", "ie" => "Interlingue",
"gm" => "Gambia", "ig" => "Igbo",
"ge" => "Georgia", "ii" => "Sichuan Yi",
"de" => "Germany", "ik" => "Inupiaq",
"gh" => "Ghana", "io" => "Ido",
"gi" => "Gibraltar", "is" => "Icelandic",
"gr" => "Greece", "it" => "Italian",
"gl" => "Greenland", "iu" => "Inuktitut",
"gd" => "Grenada", "ja" => "Japanese",
"gp" => "Guadeloupe", "jv" => "Javanese",
"gu" => "Guam", "ka" => "Georgian",
"gt" => "Guatemala", "kg" => "Kongo",
"gg" => "Guernsey", "ki" => "Kikuyu",
"gn" => "Guinea", "kj" => "Kuanyama",
"gy" => "Guyana", "kk" => "Kazakh",
"ht" => "Haiti", "kl" => "Kalaallisut",
"hn" => "Honduras", "km" => "Central Khmer",
"hk" => "Hong Kong", "kn" => "Kannada",
"hu" => "Hungary", "ko" => "Korean",
"is" => "Iceland", "kr" => "Kanuri",
"in" => "India", "ks" => "Kashmiri",
"id" => "Indonesia", "ku" => "Kurdish",
"iq" => "Iraq", "kv" => "Komi",
"ie" => "Ireland", "kw" => "Cornish",
"im" => "Isle of Man", "ky" => "Kyrgyz",
"il" => "Israel", "la" => "Latin",
"it" => "Italy", "lb" => "Luxembourgish",
"jm" => "Jamaica", "lg" => "Ganda",
"jp" => "Japan", "li" => "Limburgish",
"je" => "Jersey", "ln" => "Lingala",
"jo" => "Jordan", "lo" => "Lao",
"kz" => "Kazakhstan", "lt" => "Lithuanian",
"ke" => "Kenya", "lu" => "Luba-Katanga",
"ki" => "Kiribati", "lv" => "Latvian",
"kw" => "Kuwait", "mg" => "Malagasy",
"kg" => "Kyrgyzstan", "mh" => "Marshallese",
"la" => "Lao People's Democratic Republic", "mi" => "Maori",
"lv" => "Latvia", "mk" => "Macedonian",
"lb" => "Lebanon", "ml" => "Malayalam",
"ls" => "Lesotho", "mn" => "Mongolian",
"ly" => "Libya", "mr" => "Marathi",
"li" => "Liechtenstein", "ms" => "Malay",
"lt" => "Lithuania", "mt" => "Maltese",
"lu" => "Luxembourg", "my" => "Burmese",
"mk" => "Macedonia", "na" => "Nauru",
"mg" => "Madagascar", "nb" => "Norwegian Bokmål",
"mw" => "Malawi", "nd" => "North Ndebele",
"my" => "Malaysia", "ne" => "Nepali",
"mv" => "Maldives", "ng" => "Ndonga",
"ml" => "Mali", "nl" => "Dutch",
"mt" => "Malta", "nn" => "Norwegian Nynorsk",
"mq" => "Martinique", "no" => "Norwegian",
"mr" => "Mauritania", "nr" => "South Ndebele",
"mu" => "Mauritius", "nv" => "Navajo",
"yt" => "Mayotte", "ny" => "Chichewa",
"mx" => "Mexico", "oc" => "Occitan",
"fm" => "Micronesia, Federated States of", "oj" => "Ojibwa",
"md" => "Moldova", "om" => "Oromo",
"mc" => "Monaco", "or" => "Oriya",
"mn" => "Mongolia", "os" => "Ossetian",
"me" => "Montenegro", "pa" => "Punjabi",
"ms" => "Montserrat", "pi" => "Pali",
"ma" => "Morocco", "pl" => "Polish",
"mz" => "Mozambique", "ps" => "Pashto",
"mm" => "Myanmar", "pt" => "Portuguese",
"na" => "Namibia", "qu" => "Quechua",
"nr" => "Nauru", "rm" => "Romansh",
"np" => "Nepal", "rn" => "Rundi",
"nl" => "Netherlands", "ro" => "Romanian",
"nc" => "New Caledonia", "ru" => "Russian",
"nz" => "New Zealand", "rw" => "Kinyarwanda",
"ni" => "Nicaragua", "sa" => "Sanskrit",
"ne" => "Niger", "sc" => "Sardinian",
"ng" => "Nigeria", "sd" => "Sindhi",
"nu" => "Niue", "se" => "Northern Sami",
"no" => "Norway", "sg" => "Sango",
"om" => "Oman", "si" => "Sinhala",
"pk" => "Pakistan", "sk" => "Slovak",
"ps" => "Palestine, State of", "sl" => "Slovenian",
"pa" => "Panama", "sm" => "Samoan",
"pg" => "Papua New Guinea", "sn" => "Shona",
"py" => "Paraguay", "so" => "Somali",
"pe" => "Peru", "sq" => "Albanian",
"ph" => "Philippines", "sr" => "Serbian",
"pn" => "Pitcairn", "ss" => "Swati",
"pl" => "Poland", "st" => "Southern Sotho",
"pt" => "Portugal", "su" => "Sundanese",
"pr" => "Puerto Rico", "sv" => "Swedish",
"qa" => "Qatar", "sw" => "Swahili",
"ro" => "Romania", "ta" => "Tamil",
"ru" => "Russian Federation", "te" => "Telugu",
"rw" => "Rwanda", "tg" => "Tajik",
"re" => "Réunion", "th" => "Thai",
"sh" => "Saint Helena", "ti" => "Tigrinya",
"kn" => "Saint Kitts and Nevis", "tk" => "Turkmen",
"lc" => "Saint Lucia", "tl" => "Tagalog",
"vc" => "Saint Vincent and the Grenadines", "tn" => "Tswana",
"ws" => "Samoa",
"sm" => "San Marino",
"st" => "Sao Tome and Principe",
"sa" => "Saudi Arabia",
"sn" => "Senegal",
"rs" => "Serbia",
"sc" => "Seychelles",
"sl" => "Sierra Leone",
"sg" => "Singapore",
"sk" => "Slovakia",
"si" => "Slovenia",
"sb" => "Solomon Islands",
"so" => "Somalia",
"kr" => "Sourth Korea",
"za" => "South Africa",
"es" => "Spain",
"lk" => "Sri Lanka",
"sr" => "Suriname",
"se" => "Sweden",
"ch" => "Switzerland",
"tw" => "Taiwan",
"tj" => "Tajikistan",
"tz" => "Tanzania",
"th" => "Thailand",
"tl" => "Timor-Leste",
"tg" => "Togo",
"tk" => "Tokelau",
"to" => "Tonga", "to" => "Tonga",
"tt" => "Trinidad and Tobago", "tr" => "Turkish",
"tn" => "Tunisia", "ts" => "Tsonga",
"tr" => "Turkey", "tt" => "Tatar",
"tm" => "Turkmenistan", "tw" => "Twi",
"ug" => "Uganda", "ty" => "Tahitian",
"ua" => "Ukraine", "ug" => "Uyghur",
"ae" => "United Arab Emirates", "uk" => "Ukrainian",
"gb" => "United Kingdom", "ur" => "Urdu",
"us" => "United States", "uz" => "Uzbek",
"uy" => "Uruguay", "ve" => "Venda",
"uz" => "Uzbekistan", "vi" => "Vietnamese",
"vu" => "Vanuatu", "vo" => "Volapük",
"ve" => "Venezuela", "wa" => "Walloon",
"vn" => "Vietnam", "wo" => "Wolof",
"vg" => "Virgin Islands, British", "xh" => "Xhosa",
"vi" => "Virgin Islands, U.S.", "yi" => "Yiddish",
"ye" => "Yemen", "yo" => "Yoruba",
"zm" => "Zambia", "za" => "Zhuang",
"zw" => "Zimbabwe" "zh" => "Chinese",
"zh-cn" => "Chinese (Simplified)",
"zh-tw" => "Chinese (Traditional)",
"zu" => "Zulu"
] ]
], ],
"nsfw" => [ "nsfw" => [
"display" => "NSFW", "display" => "NSFW",
"option" => [ "option" => [
"yes" => "Yes", "yes" => "Yes",
"maybe" => "Maybe",
"no" => "No" "no" => "No"
] ]
] ]
]; ];
} }
private function get($proxy, $url, $get = []){ private function get($proxy, $url, $get = [], $use_api = false, $post_data = null, $bearer = null){
$curlproc = curl_init(); $curlproc = curl_init();
@@ -252,21 +227,29 @@ class yep{
curl_setopt($curlproc, CURLOPT_URL, $url); curl_setopt($curlproc, CURLOPT_URL, $url);
// use http2 // use http1 lol
curl_setopt($curlproc, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); curl_setopt($curlproc, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
// set ciphers
curl_setopt(
$curlproc,
CURLOPT_SSL_CIPHER_LIST,
"aes_128_gcm_sha_256,chacha20_poly1305_sha_256,aes_256_gcm_sha_384,ecdhe_ecdsa_aes_128_gcm_sha_256,ecdhe_rsa_aes_128_gcm_sha_256,ecdhe_ecdsa_chacha20_poly1305_sha_256,ecdhe_rsa_chacha20_poly1305_sha_256,ecdhe_ecdsa_aes_256_gcm_sha_384,ecdhe_rsa_aes_256_gcm_sha_384,ecdhe_ecdsa_aes_256_sha,ecdhe_ecdsa_aes_128_sha,ecdhe_rsa_aes_128_sha,ecdhe_rsa_aes_256_sha,rsa_aes_128_gcm_sha_256,rsa_aes_256_gcm_sha_384,rsa_aes_128_sha,rsa_aes_256_sha"
);
curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding curl_setopt($curlproc, CURLOPT_ENCODING, ""); // default encoding
if($use_api){
$post_data = json_encode($post_data);
curl_setopt($curlproc, CURLOPT_HTTPHEADER,
["Content-Type: application/json",
"Authorization: Bearer $bearer",
"Content-Length: " . strlen($post_data)]
);
curl_setopt($curlproc, CURLOPT_POST, true);
curl_setopt($curlproc, CURLOPT_POSTFIELDS, $post_data);
}else{
curl_setopt($curlproc, CURLOPT_HTTPHEADER, curl_setopt($curlproc, CURLOPT_HTTPHEADER,
["User-Agent: " . config::USER_AGENT, ["User-Agent: " . config::USER_AGENT,
"Accept: */*", "Accept: */*",
"Accept-Language: en-US,en;q=0.5", "Accept-Language: en-US,en;q=0.9",
"Accept-Encoding: gzip, deflate, br, zstd", "Accept-Encoding: gzip, deflate, br, zstd",
"Referer: https://yep.com/", "Referer: https://yep.com/",
"Origin: https://yep.com", "Origin: https://yep.com",
@@ -276,8 +259,11 @@ class yep{
"Sec-Fetch-Mode: cors", "Sec-Fetch-Mode: cors",
"Sec-Fetch-Site: same-site", "Sec-Fetch-Site: same-site",
"Priority: u=4", "Priority: u=4",
"Pragma: no-cache",
"Cache-Control: no-cache",
"TE: trailers"] "TE: trailers"]
); );
}
curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true); curl_setopt($curlproc, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2); curl_setopt($curlproc, CURLOPT_SSL_VERIFYHOST, 2);
@@ -302,22 +288,17 @@ class yep{
public function web($get){ public function web($get){
if(config::YEP_USE_API){
return $this->web_api($get);
}
$search = $get["s"]; $search = $get["s"];
if(strlen($search) === 0){ if(strlen($search) === 0){
throw new Exception("Search term is empty!"); throw new Exception("Search term is empty!");
} }
$country = $get["country"];
$nsfw = $get["nsfw"];
switch($nsfw){
case "yes": $nsfw = "off"; break;
case "maybe": $nsfw = "moderate"; break;
case "no": $nsfw = "strict"; break;
}
$out = [ $out = [
"status" => "ok", "status" => "ok",
"spelling" => [ "spelling" => [
@@ -334,23 +315,25 @@ class yep{
"related" => [] "related" => []
]; ];
// parse filters
$filters = [
"limit" => 100, // wwwwwwwwwwwwwww
"query" => $search,
];
if($get["nsfw"] == "no"){ $filters["safeSearch"] = "moderate"; }
if($get["lang"] != "any"){ $filters["hl"] = $get["lang"]; }
try{ try{
// https://api.yep.com/fs/2/search?client=web&gl=CA&no_correct=false&q=undefined+variable+javascript&safeSearch=off&type=web // https://api.yep.com/search?limit=20&query=asmr
$json = $json =
$this->get( $this->get(
$this->backend->get_ip(), $this->backend->get_ip(),
"https://api.yep.com/fs/2/search", "https://api.yep.com/search",
[ $filters
"client" => "web",
"gl" => $country == "all" ? $country : strtoupper($country),
"limit" => "99999",
"no_correct" => "false",
"q" => $search,
"safeSearch" => $nsfw,
"type" => "web"
]
); );
}catch(Exception $error){ }catch(Exception $error){
throw new Exception("Failed to fetch JSON"); throw new Exception("Failed to fetch JSON");
@@ -414,7 +397,7 @@ class yep{
) )
), ),
"url" => $item["url"], "url" => $item["url"],
"date" => strtotime($item["first_seen"]), "date" => null,
"type" => "web", "type" => "web",
"thumb" => [ "thumb" => [
"url" => null, "url" => null,
@@ -428,83 +411,11 @@ class yep{
} }
} }
if(isset($json[1]["featured_news"])){
foreach($json[1]["featured_news"] as $news){
$out["news"][] = [
"title" => $news["title"],
"description" =>
$this->titledots(
strip_tags(
html_entity_decode(
$news["snippet"]
)
)
),
"date" => strtotime($news["first_seen"]),
"thumb" =>
isset($news["img"]) ?
[
"url" => $this->unshiturl($news["img"]),
"ratio" => "16:9"
] :
[
"url" => null,
"ratio" => null
],
"url" => $news["url"]
];
}
}
if(isset($json[1]["featured_images"])){
foreach($json[1]["featured_images"] as $image){
if(
$image["width"] !== 0 &&
$image["height"] !== 0
){
$thumb_width = $image["width"] >= 260 ? 260 : $image["width"];
$thumb_height = ceil($image["height"] * ($thumb_width / $image["width"]));
$width = $image["width"];
$height = $image["height"];
}else{
$thumb_width = null;
$thumb_height = null;
$width = null;
$height = null;
}
$out["image"][] = [
"title" => $image["title"],
"source" => [
[
"url" => $image["image_id"],
"width" => $width,
"height" => $height
],
[
"url" => $image["src"],
"width" => $thumb_width,
"height" => $thumb_height
]
],
"url" => $image["host_page"]
];
}
}
return $out; return $out;
} }
private function web_api($get){
public function image($get){
$search = $get["s"]; $search = $get["s"];
if(strlen($search) === 0){ if(strlen($search) === 0){
@@ -512,142 +423,53 @@ class yep{
throw new Exception("Search term is empty!"); throw new Exception("Search term is empty!");
} }
$country = $get["country"];
$nsfw = $get["nsfw"];
switch($nsfw){
case "yes": $nsfw = "off"; break;
case "maybe": $nsfw = "moderate"; break;
case "no": $nsfw = "strict"; break;
}
$out = [ $out = [
"status" => "ok", "status" => "ok",
"spelling" => [
"type" => "no_correction",
"using" => null,
"correction" => null
],
"npt" => null, "npt" => null,
"image" => [] "answer" => [],
"web" => [],
"image" => [],
"video" => [],
"news" => [],
"related" => []
]; ];
// parse filters
$filters = [
"query" => $search,
"limit" => 99 // for some reason they dont show anything sometimes at 100 results
];
if($get["nsfw"] == "no"){ $filters["safe_search"] = true; }
if($get["lang"] != "any"){ $filters["language"] = [ $get["lang"] ]; }
// add api key
$key_data = $this->backend->get_key();
try{ try{
$json = $json =
$this->get( $this->get(
$this->backend->get_ip(), // no nextpage! $this->backend->get_ip($key_data["increment"]),
"https://api.yep.com/fs/2/search", "https://platform.yep.com/api/search",
[ [],
"client" => "web", true,
"gl" => $country == "all" ? $country : strtoupper($country), $filters,
"no_correct" => "false", $key_data["key"]
"q" => $search,
"safeSearch" => $nsfw,
"type" => "images"
]
); );
}catch(Exception $error){ }catch(Exception $error){
throw new Exception("Failed to fetch JSON"); throw new Exception("Failed to fetch JSON");
} }
$this->detect_cf($json); // should never happen
//$this->detect_cf($json);
$json = json_decode($json, true);
if($json === null){
throw new Exception("Failed to decode JSON");
}
if(isset($json[1]["results"])){
foreach($json[1]["results"] as $item){
if(
$item["width"] !== 0 &&
$item["height"] !== 0
){
$thumb_width = $item["width"] >= 260 ? 260 : $item["width"];
$thumb_height = ceil($item["height"] * ($thumb_width / $item["width"]));
$width = $item["width"];
$height = $item["height"];
}else{
$thumb_width = null;
$thumb_height = null;
$width = null;
$height = null;
}
$out["image"][] = [
"title" => $item["title"],
"source" => [
[
"url" => $item["image_id"],
"width" => $width,
"height" => $height
],
[
"url" => $item["src"],
"width" => $thumb_width,
"height" => $thumb_height
]
],
"url" => $item["host_page"]
];
}
}
return $out;
}
public function news($get){
$search = $get["s"];
if(strlen($search) === 0){
throw new Exception("Search term is empty!");
}
$country = $get["country"];
$nsfw = $get["nsfw"];
switch($nsfw){
case "yes": $nsfw = "off"; break;
case "maybe": $nsfw = "moderate"; break;
case "no": $nsfw = "strict"; break;
}
$out = [
"status" => "ok",
"npt" => null,
"news" => []
];
try{
// https://api.yep.com/fs/2/search?client=web&gl=CA&no_correct=false&q=undefined+variable+javascript&safeSearch=off&type=web
$json =
$this->get(
$this->backend->get_ip(),
"https://api.yep.com/fs/2/search",
[
"client" => "web",
"gl" => $country == "all" ? $country : strtoupper($country),
"limit" => "99999",
"no_correct" => "false",
"q" => $search,
"safeSearch" => $nsfw,
"type" => "news"
]
);
}catch(Exception $error){
throw new Exception("Failed to fetch JSON");
}
$this->detect_cf($json);
$json = json_decode($json, true); $json = json_decode($json, true);
//$json = json_decode(file_get_contents("scraper/yep.json"), true); //$json = json_decode(file_get_contents("scraper/yep.json"), true);
@@ -657,35 +479,54 @@ class yep{
throw new Exception("Failed to decode JSON"); throw new Exception("Failed to decode JSON");
} }
if(isset($json[1]["results"])){ if(isset($json["error"])){
foreach($json[1]["results"] as $item){
$out["news"][] = [ throw new Exception("Yep API returned an error: " . $json["error"]);
}
if(isset($json["errors"])){
throw new Exception("Yep API returned the following errors: {$json["message"]}");
}
if(
isset($json["success"]) &&
$json["success"] !== true
){
throw new Exception("Yep API returned a false-y success value");
}
if(!isset($json["results"])){
throw new Exception("Yep API did not return a results object");
}
foreach($json["results"] as $item){
if(
$item["url"] === null ||
$item["url"] == ""
){
// sometimes API fucks up
continue;
}
$out["web"][] = [
"title" => $item["title"], "title" => $item["title"],
"author" => null, "description" => $item["description"],
"description" => "url" => $item["url"],
$this->titledots( "date" => null,
strip_tags( "type" => "web",
html_entity_decode( "thumb" => [
$item["snippet"]
)
)
),
"date" => strtotime($item["first_seen"]),
"thumb" =>
isset($item["img"]) ?
[
"url" => $this->unshiturl($item["img"]),
"ratio" => "16:9"
] :
[
"url" => null, "url" => null,
"ratio" => null "ratio" => null
], ],
"url" => $item["url"] "sublink" => [],
"table" => []
]; ];
} }
}
return $out; return $out;
} }
+609 -895
View File
File diff suppressed because it is too large. Load diff
+114 -50
View File
@@ -87,14 +87,6 @@ $settings = [
"value" => "kagi", "value" => "kagi",
"text" => "Kagi" "text" => "Kagi"
], ],
[
"value" => "qwant",
"text" => "Qwant"
],
[
"value" => "ghostery",
"text" => "Ghostery"
],
[ [
"value" => "yep", "value" => "yep",
"text" => "Yep" "text" => "Yep"
@@ -133,30 +125,26 @@ $settings = [
"value" => "google", "value" => "google",
"text" => "Google" "text" => "Google"
], ],
[
"value" => "google_api",
"text" => "Google API"
],
[
"value" => "google_cse",
"text" => "Google CSE"
],
[
"value" => "yahoo_japan",
"text" => "Yahoo! JAPAN",
],
[ [
"value" => "startpage", "value" => "startpage",
"text" => "Startpage" "text" => "Startpage"
], ],
[
"value" => "qwant",
"text" => "Qwant"
],
[
"value" => "ghostery",
"text" => "Ghostery"
],
[ [
"value" => "yep", "value" => "yep",
"text" => "Yep" "text" => "Yep"
], ],
[
"value" => "greppr",
"text" => "Greppr"
],
[
"value" => "crowdview",
"text" => "Crowdview"
],
[ [
"value" => "mwmbl", "value" => "mwmbl",
"text" => "Mwmbl" "text" => "Mwmbl"
@@ -165,6 +153,18 @@ $settings = [
"value" => "mojeek", "value" => "mojeek",
"text" => "Mojeek" "text" => "Mojeek"
], ],
[
"value" => "naver",
"text" => "Naver"
],
[
"value" => "baidu",
"text" => "Baidu"
],
[
"value" => "coccoc",
"text" => "Cốc Cốc"
],
[ [
"value" => "solofield", "value" => "solofield",
"text" => "Solofield" "text" => "Solofield"
@@ -174,12 +174,12 @@ $settings = [
"text" => "Marginalia" "text" => "Marginalia"
], ],
[ [
"value" => "wiby", "value" => "purili",
"text" => "wiby" "text" => "Purili"
], ],
[ [
"value" => "curlie", "value" => "wiby",
"text" => "Curlie" "text" => "wiby"
] ]
] ]
], ],
@@ -203,26 +203,62 @@ $settings = [
"value" => "google", "value" => "google",
"text" => "Google" "text" => "Google"
], ],
[
"value" => "google_api",
"text" => "Google API"
],
[
"value" => "google_cse",
"text" => "Google CSE"
],
[
"value" => "yahoo_japan",
"text" => "Yahoo! JAPAN",
],
[ [
"value" => "startpage", "value" => "startpage",
"text" => "Startpage" "text" => "Startpage"
], ],
[ [
"value" => "qwant", "value" => "naver",
"text" => "Qwant" "text" => "Naver"
], ],
[ [
"value" => "yep", "value" => "baidu",
"text" => "Yep" "text" => "Baidu"
], ],
[ [
"value" => "solofield", "value" => "solofield",
"text" => "Solofield" "text" => "Solofield"
], ],
/*[ [
"value" => "pinterest", "value" => "pinterest",
"text" => "Pinterest" "text" => "Pinterest"
],*/ ],
[
"value" => "flickr",
"text" => "Flickr"
],
[
"value" => "pexels",
"text" => "Pexels"
],
[
"value" => "pixabay",
"text" => "Pixabay"
],
[
"value" => "unsplash",
"text" => "Unsplash"
],
[
"value" => "fivehpx",
"text" => "500px"
],
[
"value" => "vsco",
"text" => "VSCO"
],
[ [
"value" => "imgur", "value" => "imgur",
"text" => "Imgur" "text" => "Imgur"
@@ -241,6 +277,14 @@ $settings = [
"value" => "yt", "value" => "yt",
"text" => "YouTube" "text" => "YouTube"
], ],
[
"value" => "vimeo",
"text" => "Vimeo"
],
[
"value" => "sepiasearch",
"text" => "Sepia Search"
],
[ [
"value" => "ddg", "value" => "ddg",
"text" => "DuckDuckGo" "text" => "DuckDuckGo"
@@ -257,13 +301,29 @@ $settings = [
"value" => "google", "value" => "google",
"text" => "Google" "text" => "Google"
], ],
[
"value" => "yahoo_japan",
"text" => "Yahoo! JAPAN",
],
[ [
"value" => "startpage", "value" => "startpage",
"text" => "Startpage" "text" => "Startpage"
], ],
[ [
"value" => "qwant", "value" => "naver",
"text" => "Qwant" "text" => "Naver"
],
[
"value" => "baidu",
"text" => "Baidu"
],
[
"value" => "coccoc",
"text" => "Cốc Cốc"
],
[
"value" => "purili",
"text" => "Purili"
], ],
[ [
"value" => "solofield", "value" => "solofield",
@@ -287,21 +347,21 @@ $settings = [
"value" => "google", "value" => "google",
"text" => "Google" "text" => "Google"
], ],
[
"value" => "yahoo_japan",
"text" => "Yahoo! JAPAN",
],
[ [
"value" => "startpage", "value" => "startpage",
"text" => "Startpage" "text" => "Startpage"
], ],
[ /*[
"value" => "qwant",
"text" => "Qwant"
],
[
"value" => "yep",
"text" => "Yep"
],
[
"value" => "mojeek", "value" => "mojeek",
"text" => "Mojeek" "text" => "Mojeek"
],*/
[
"value" => "baidu",
"text" => "Baidu"
] ]
] ]
], ],
@@ -312,11 +372,15 @@ $settings = [
[ [
"value" => "sc", "value" => "sc",
"text" => "SoundCloud" "text" => "SoundCloud"
]//, ],
//[ [
// "value" => "spotify", "value" => "swisscows",
// "text" => "Spotify" "text" => "Swisscows"
//] ]
/*[
"value" => "spotify",
"text" => "Spotify"
]*/
] ]
] ]
] ]
+14 -4
View File
@@ -22,7 +22,7 @@ var list = [];
var pinged_list = []; var pinged_list = [];
var reqs = 0; var reqs = 0;
var errors = 0; var errors = 0;
var sort = 6; // highest version first var sort = 12;
// check for instance redirect stuff // check for instance redirect stuff
var redir = []; var redir = [];
@@ -68,8 +68,8 @@ table.innerHTML =
'<th class="extend">Server</th>' + '<th class="extend">Server</th>' +
'<th>Address</th>' + '<th>Address</th>' +
'<th>Bot protection</th>' + '<th>Bot protection</th>' +
'<th title="Amount of legit requests processed since the last APCU cache clear (usually happens at midnight)">Real reqs (?)</th>' + '<th title="Amount of legit requests processed within the last 24 hours">Real reqs (?)</th>' +
'<th title="Amount of filtered requests processed since the last APCU cache clear (usually happens at midnight)">Bot reqs (?)</th>' + '<th title="Amount of filtered requests processed within the last 24 hours">Bot reqs (?)</th>' +
'<th>API</th>' + '<th>API</th>' +
'<th><div class="arrow up"></div>Version</th>' + '<th><div class="arrow up"></div>Version</th>' +
'</tr>' + '</tr>' +
@@ -80,6 +80,15 @@ document.body.insertBefore(table, quote.nextSibling);
// handle sorting clicks // handle sorting clicks
var tbody = table.getElementsByTagName("tbody")[0]; var tbody = table.getElementsByTagName("tbody")[0];
// server popup without inline javascript
tbody.addEventListener('click', function(event){
var row = event.target.closest('.show_server');
if(!row) return;
show_server(row.id);
})
var th = table.getElementsByTagName("th"); var th = table.getElementsByTagName("th");
for(var i=0; i<th.length; i++){ for(var i=0; i<th.length; i++){
@@ -343,7 +352,7 @@ function render_list(){
for(var k=0; k<sorted_list.length; k++){ for(var k=0; k<sorted_list.length; k++){
html += '<tr onclick="show_server(' + sorted_list[k].index + ');">'; html += '<tr class="show_server" id="' + sorted_list[k].index + '">';
for(var i=0; i<7; i++){ for(var i=0; i<7; i++){
@@ -406,6 +415,7 @@ function render_list(){
console.log(html); console.log(html);
tbody.innerHTML = html; tbody.innerHTML = html;
} }
var popup_bg = document.getElementById("popup-bg"); var popup_bg = document.getElementById("popup-bg");
+28
View File
@@ -1013,6 +1013,23 @@ table tr a:last-child{
font-weight:bold; font-weight:bold;
} }
/* booru */
#images .image .i-title-wrapper{
margin-bottom:7px;
white-space:nowrap;
overflow:hidden;
text-overflow:ellipsis;
}
#images .image .i-title-wrapper a{
display:inline;
}
#images .image .i-title-wrapper .title{
display:inline;
margin-bottom:0;
}
/* end booru */
#images .image .description{ #images .image .description{
overflow:hidden; overflow:hidden;
height:45px; height:45px;
@@ -1113,6 +1130,17 @@ table tr a:last-child{
/* /*
Settings page Settings page
*/ */
.web .settings select{
all:unset;
user-select:none;
display:block;
border:1px solid var(--504945);
border-radius:2px;
font-size:14px;
padding:0 20px 0 4px;
height:24px;
}
.web .settings{ .web .settings{
margin-top:17px; margin-top:17px;
border:1px dashed var(--504945); border:1px dashed var(--504945);
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+33 -33
View File
@@ -12,10 +12,9 @@
--ebdbb2:#ebdbb2; --ebdbb2:#ebdbb2;
} }
body{ body{
padding:15px 4% 40px; padding:15px 4% 40px;
margin:unset;
} }
h1, h2, h3, h4, h5, h6{ h1, h2, h3, h4, h5, h6{
@@ -41,7 +40,6 @@ h3,h4,h5,h6{
background:#723c0b; background:#723c0b;
} }
.searchbox input{ .searchbox input{
all:unset; all:unset;
line-height:36px; line-height:36px;
@@ -96,7 +94,6 @@ h3,h4,h5,h6{
display:inline-block; display:inline-block;
} }
.tabs .tab.selected{ .tabs .tab.selected{
border-bottom:2px solid #fc92a5; border-bottom:2px solid #fc92a5;
} }
@@ -106,7 +103,7 @@ h3,h4,h5,h6{
padding-bottom:12px; padding-bottom:12px;
padding-top:7px; padding-top:7px;
margin-bottom:7px; margin-bottom:7px;
background-color:#232525 background-color:#232525;
} }
.filters .filter{ .filters .filter{
@@ -169,7 +166,6 @@ h3,h4,h5,h6{
font-size:12px; font-size:12px;
} }
.web .hover{ .web .hover{
display:block; display:block;
text-decoration:none; text-decoration:none;
@@ -193,16 +189,13 @@ h3,h4,h5,h6{
color:#9760b1 !important; color:#9760b1 !important;
} }
.web .text-result .greentext{ .web .text-result .greentext{
font-size:14px; font-size:14px;
color:var(--bdae93); color:var(--bdae93);
} }
/* favicon */ /* favicon */
.favicon-dropdown a{ .favicon-dropdown a{
text-decoration:none; text-decoration:none;
color:#d3d0c1; color:#d3d0c1;
@@ -211,37 +204,31 @@ h3,h4,h5,h6{
font-size:13px; font-size:13px;
} }
.web .favicon img, .favicon-dropdown img{
.web .favicon img,
.favicon-dropdown img{
margin:3px 7px 0 0; margin:3px 7px 0 0;
height:16px; height:16px;
font-size:12px; font-size:12px;
line-height:16px;; line-height:16px;
display:block; display:block;
text-align:left; text-align:left;
} }
.web .sublinks{ .web .sublinks{
padding:17px 10px; padding:17px 10px;
font-size:15px; font-size:15px;
color:var(--#928374); color:var(--#928374);
} }
.web .text-result .sublinks:last-child{ .web .text-result .sublinks:last-child{
padding-bottom:0; padding-bottom:0;
} }
/* Wikipedia head */ /* Wikipedia head */
.wiki-head{ .wiki-head{
padding:5px; padding:5px;
background-color: #322f2b background-color:#322f2b;
} }
/* /*
Images tab Images tab
*/ */
@@ -257,8 +244,6 @@ h3,h4,h5,h6{
float:left; float:left;
} }
#images .image .title{ #images .image .title{
white-space:nowrap; white-space:nowrap;
overflow:hidden; overflow:hidden;
@@ -267,7 +252,6 @@ h3,h4,h5,h6{
color:var(--bdae93); color:var(--bdae93);
} }
#popup-status{ #popup-status{
display:none; display:none;
position:fixed; position:fixed;
@@ -283,40 +267,56 @@ h3,h4,h5,h6{
Settings page Settings page
*/ */
.web .settings-submit a{ .web .settings-submit a{
margin-right:17px; margin-right:17px;
color:#bdae93; color:#bdae93;
} }
/* /*
Responsive image Responsive image
*/ */
@media only screen and (max-width: 1454px){ #images .image-wrapper{ width:25%; } } @media only screen and (max-width:1454px){
@media only screen and (max-width: 1161px){ #images .image-wrapper{ width:25%; } } #images .image-wrapper{
@media only screen and (max-width: 750px){ #images .image-wrapper{ width:50%; } } width:25%;
@media only screen and (max-width: 450px){ #images .image-wrapper{ width:100%; } } }
}
@media only screen and (max-width:1161px){
#images .image-wrapper{
width:25%;
}
}
@media only screen and (max-width:750px){
#images .image-wrapper{
width:50%;
}
}
@media only screen and (max-width:450px){
#images .image-wrapper{
width:100%;
}
}
/* /*
Responsive design Responsive design
*/ */
@media only screen and (max-width:1550px){ @media only screen and (max-width:1550px){
.web .left, .web .left,
.searchbox{ .searchbox{
width:60%; width:60%;
} }
} }
@media only screen and (max-width: 1000px){ @media only screen and (max-width:1100px){
.web .left,
.searchbox{
width:100%;
}
} }
.type{ .type{
color:var(--bdae93); color:var(--bdae93);
} }
}
+17
View File
@@ -0,0 +1,17 @@
:root{
--1d2021: #101010;
--282828: #1a1a1a;
--3c3836: #1e1e1e;
--504945: #232323;
--928374: #949494;
--a89984: #d2d2d2;
--bdae93: #d2d2d2;
--8ec07c: #99c794;
--ebdbb2: #d2d2d2;
--comment: #5f6364;
--default: #cccece;
--keyword: #c594c5;
--string: #99c794;
}
+20
View File
@@ -0,0 +1,20 @@
:root{
/* background */
--1d2021: #191724;
--282828: #1f1d2e;
--3c3836: #26233a;
--504945: #524f67;
/* font */
--928374: #e0def4;
--a89984: #908caa;
--bdae93: #6e6a86;
--8ec07c: #9ccfd8;
--ebdbb2: #f6c177;
/* code highlighter */
--comment: #6e6a86;
--default: #c4a7e7;
--keyword: #31748f;
--string: #f6c177;
}
+40
View File
@@ -0,0 +1,40 @@
:root
{
--accent : #f79e98;
--1d2021 : #180d0c;
--282828 : #180d0c;
--3c3836 : #251615;
--504945 : #251615;
--928374 : var(--accent);
--a89984 : #d8c5c4;
--bdae93 : #d8c5c4;
--8ec07c : var(--accent);
--ebdbb2 : #d8c5c4;
--comment: #928374;
--default: #DCC9BC;
--keyword: #F07342;
--string : var(--accent);
--green : #959A6B;
--yellow : #E39C45;
--red : #CF223E;
--white : var(--a89984);
--black : var(--1d2021);
--hover : #b18884
}
a.link, a { color: var(--accent); text-decoration: none; }
.searchbox { width: 23%; }
.filters filter select { color: #E39C45; }
.web .separator::before { color: var(--white) }
.searchbox input[type="text"]::placeholder { color: var(--white); }
a.link:hover
{
color: var(--hover);
text-shadow: 0 0 .2rem var(--hover);
}
.code-inline
{ border-color: var(--default); font-family: monospace;}
.home #center a
{ color: var(--accent); }
.home .subtext
{ color: var(--white); }
+25
View File
@@ -0,0 +1,25 @@
:root {
/* background */
--1d2021: #232a2e;
--282828: #2b3337;
--3c3836: #374145;
--504945: #4a585c;
/* font */
--928374: #839e9a;
--a89984: #adc9bc;
--bdae93: #f8f9e8;
--8ec07c: #cbe3b3;
--ebdbb2: #adc9bc;
/* code highlighter */
--comment: #839e9a;
--default: #f8f9e8;
--keyword: #6f8788;
--string: #cbe3b3;
/* color codes for instance list */
--green: #cbe3b3;
--yellow: #f5d098;
--red: #f57f82;
}
+12 -3
View File
@@ -1,9 +1,18 @@
<a href="/" class="link">&lt; Go back</a> <a href="/" class="link">&lt; Go back</a>
<h1>Donate to the project</h1> <h1>Donate to the project</h1>
This project takes up most of my free time and costs money to run. If you feel like this project is worthy of a donation, please do so using the resources on this page!<br><br> This project takes up most of my free time and costs (a lot of) money to run. If you feel like this project is worthy of a donation, please do so using the resources on this page!<br><br>
To run smoothly, 4get requires 20$/month for the server fees and 15-25$ for residential proxies. I also have plans to build my own index to provide better search results; funds raised here will go directly towards better hardware for these purposes. According to the number of captchas solved, <a href="https://4get.ca">4get.ca</a> serves between 800-1400 users every day, or around 7000-10000 searches! To run smoothly, 4get requires:
<ul>
<li>$350/year for server hosting at OVH</li>
<li>$60-80/month for proxies</li>
<li>$54/month for home internet</li>
</ul>
I also bought a dedicated machine for browser rendering hosted at home coming at around $380.<br><br>
According to the number of captchas solved, <a href="https://4get.ca">4get.ca</a> currently serves between <a href="https://4get.ca/ami4get">2000-2500 users every day</a>, and serves around <a href="https://4get.ca/api/v2/graph">20,000-22,000 searches</a>!
<h2>Ko-fi</h2> <h2>Ko-fi</h2>
This is the most convenient way to donate. Supports PayPal. This is the most convenient way to donate. Supports PayPal.
@@ -17,4 +26,4 @@ Due to popular demand, I have added an XMR addy. I'm planning to reward users wh
<li><a href="monero:85G95fXXfuh72oQd9LmdMHNqDTqsUPKkLCBEYE7XnnMLPvBfkdnan2FVTmVAwT4JEuXsRL7xPgqzuV2YdbFkWmxK2FgGBmB?tx_description=4get+donation">85G95fXXfuh72oQd9LmdMHNqDTqsUPKkLCBEYE7XnnMLPvBfkdnan2FVTmVAwT4JEuXsRL7xPgqzuV2YdbFkWmxK2FgGBmB</a></li> <li><a href="monero:85G95fXXfuh72oQd9LmdMHNqDTqsUPKkLCBEYE7XnnMLPvBfkdnan2FVTmVAwT4JEuXsRL7xPgqzuV2YdbFkWmxK2FgGBmB?tx_description=4get+donation">85G95fXXfuh72oQd9LmdMHNqDTqsUPKkLCBEYE7XnnMLPvBfkdnan2FVTmVAwT4JEuXsRL7xPgqzuV2YdbFkWmxK2FgGBmB</a></li>
</ul> </ul>
Thank you all for your generous support!! I'm immensely grateful for every donation. Thank you all for your generous support!!
+1 -1
View File
@@ -15,7 +15,7 @@
<div class="navigation"> <div class="navigation">
<a href="/">Home</a> <a href="/">Home</a>
<a href="/settings">Settings</a> <a href="/settings">Settings</a>
<a href="https://git.lolcat.ca/lolcat/4get_news" target="_BLANK">News</a> <a href="https://bsky.app/profile/4getisdown.bsky.social" target="_BLANK">News</a>
</div> </div>
<form method="GET" autocomplete="off"> <form method="GET" autocomplete="off">
<div class="searchbox"> <div class="searchbox">
Loaded 100 of 103 files, more files were not shown because too many files have changed in this diff. Show more