1
0

backend.php 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. <?php
  2. class backend{
  3. public function __construct($scraper){
  4. $this->scraper = $scraper;
  5. $this->db = null;
  6. $this->db_path_prefix = "data/sessions/";
  7. $this->db_path = "{$this->db_path_prefix}{$scraper}.sqlite";
  8. }
  9. /*
  10. Proxy stuff
  11. */
  12. public function get_ip($proxy_index_raw = null){
  13. $pool = constant("config::PROXY_" . strtoupper($this->scraper));
  14. if($pool === false){
  15. // we don't want a proxy, fuck off!
  16. return 'raw_ip::::';
  17. }
  18. // indent
  19. if($proxy_index_raw === null){
  20. $proxy_index_raw = apcu_inc("p." . $this->scraper);
  21. }
  22. $proxylist = file_get_contents("data/proxies/" . $pool . ".txt");
  23. $proxylist = explode("\n", $proxylist);
  24. // ignore empty or commented lines
  25. $proxylist = array_filter($proxylist, function($entry){
  26. $entry = ltrim($entry);
  27. return strlen($entry) > 0 && substr($entry, 0, 1) != "#";
  28. });
  29. $proxylist = array_values($proxylist);
  30. if(count($proxylist) === 0){
  31. throw new Exception("A proxy list was specified but it's empty!");
  32. }
  33. //echo $proxylist[$proxy_index_raw % count($proxylist)];
  34. return $proxylist[$proxy_index_raw % count($proxylist)];
  35. }
  36. // this function is also called directly on nextpage
  37. public function assign_proxy(&$curlproc, string $ip){
  38. // parse proxy line
  39. [
  40. $type,
  41. $address,
  42. $port,
  43. $username,
  44. $password
  45. ] = explode(":", $ip, 5);
  46. switch($type){
  47. case "raw_ip":
  48. return;
  49. break;
  50. case "http":
  51. case "https":
  52. curl_setopt($curlproc, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
  53. curl_setopt($curlproc, CURLOPT_PROXY, $type . "://" . $address . ":" . $port);
  54. break;
  55. case "socks4":
  56. curl_setopt($curlproc, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4);
  57. curl_setopt($curlproc, CURLOPT_PROXY, $address . ":" . $port);
  58. break;
  59. case "socks5":
  60. curl_setopt($curlproc, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
  61. curl_setopt($curlproc, CURLOPT_PROXY, $address . ":" . $port);
  62. break;
  63. case "socks4a":
  64. curl_setopt($curlproc, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS4A);
  65. curl_setopt($curlproc, CURLOPT_PROXY, $address . ":" . $port);
  66. break;
  67. case "socks5_hostname":
  68. case "socks5h":
  69. case "socks5a":
  70. curl_setopt($curlproc, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME);
  71. curl_setopt($curlproc, CURLOPT_PROXY, $address . ":" . $port);
  72. break;
  73. }
  74. if($username != ""){
  75. curl_setopt($curlproc, CURLOPT_PROXYUSERPWD, $username . ":" . $password);
  76. }
  77. }
  78. // API key rotation
  79. public function get_key(){
  80. $keys = file_get_contents("data/api_keys/" . $this->scraper . ".txt");
  81. $keys = explode("\n", $keys);
  82. $keys = array_filter($keys, function($entry){
  83. $entry = ltrim($entry);
  84. return strlen($entry) > 0 && substr($entry, 0, 1) != "#";
  85. });
  86. $keys = array_values($keys);
  87. if(count($keys) === 0){
  88. throw new Exception("Please specify API keys in data/api_keys/" . $this->scraper . ".txt");
  89. }
  90. $increment = apcu_inc("s." . $this->scraper) % count($keys);
  91. return [
  92. "key" => $keys[$increment],
  93. "increment" => $increment
  94. ];
  95. }
  96. /*
  97. Next page stuff
  98. */
  99. public function store(string $payload, string $page, string $proxy){
  100. $key = sodium_crypto_secretbox_keygen();
  101. $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
  102. $requestid = apcu_inc("requestid");
  103. apcu_store(
  104. $page[0] . "." . // first letter of page name
  105. $this->scraper . // scraper name
  106. $requestid,
  107. [
  108. $nonce,
  109. $proxy,
  110. // compress and encrypt
  111. sodium_crypto_secretbox(
  112. gzdeflate($payload),
  113. $nonce,
  114. $key
  115. )
  116. ],
  117. 900 // cache information for 15 minutes
  118. );
  119. return
  120. $this->scraper . $requestid . "." .
  121. rtrim(strtr(base64_encode($key), '+/', '-_'), '=');
  122. }
  123. public function get(string $npt, string $page){
  124. $page = $page[0];
  125. $explode = explode(".", $npt, 2);
  126. if(count($explode) !== 2){
  127. throw new Exception("Malformed nextPageToken!");
  128. }
  129. $apcu = $page . "." . $explode[0];
  130. $key = $explode[1];
  131. $payload = apcu_fetch($apcu);
  132. if($payload === false){
  133. throw new Exception("The next page token is invalid or has expired!");
  134. }
  135. $key =
  136. base64_decode(
  137. str_pad(
  138. strtr($key, '-_', '+/'),
  139. strlen($key) % 4,
  140. '=',
  141. STR_PAD_RIGHT
  142. )
  143. );
  144. // decrypt and decompress data
  145. $payload[2] =
  146. gzinflate(
  147. sodium_crypto_secretbox_open(
  148. $payload[2], // data
  149. $payload[0], // nonce
  150. $key
  151. )
  152. );
  153. if($payload[2] === false){
  154. throw new Exception("The next page token is invalid or has expired!");
  155. }
  156. // remove the key after using successfully
  157. apcu_delete($apcu);
  158. return [
  159. $payload[2], // data
  160. $payload[1] // proxy
  161. ];
  162. }
  163. //
  164. // Sessions
  165. //
  166. public function session_init_db($force_init = false){
  167. if(
  168. $force_init ||
  169. $this->db === null
  170. ){
  171. $this->db = new SQLite3($this->db_path);
  172. // create table if it doesnt exist
  173. $this->db->exec(
  174. "CREATE TABLE IF NOT EXISTS sessions ( " .
  175. "id INTEGER PRIMARY KEY AUTOINCREMENT, " .
  176. "created_at INTEGER NOT NULL, " .
  177. "proxy TEXT NOT NULL, " .
  178. "headers_json TEXT NOT NULL" .
  179. ")"
  180. );
  181. return true;
  182. }
  183. return false;
  184. }
  185. public function session_count(){
  186. $this->session_init_db();
  187. $stmt = $this->db->prepare(
  188. "SELECT COUNT(*) as count FROM sessions"
  189. );
  190. $result = $stmt->execute();
  191. $row = $result->fetchArray(SQLITE3_ASSOC);
  192. return (int)($row["count"] ?? 0);
  193. }
  194. public function session_write($proxy, $headers){
  195. $this->session_init_db();
  196. $stmt = $this->db->prepare(
  197. "INSERT INTO sessions ( " .
  198. "created_at, " .
  199. "proxy, " .
  200. "headers_json " .
  201. ") VALUES ( " .
  202. ":created_at, " .
  203. ":proxy, " .
  204. ":headers_json" .
  205. ")"
  206. );
  207. $stmt->bindValue(':created_at', time(), SQLITE3_INTEGER);
  208. $stmt->bindValue(':proxy', $proxy, SQLITE3_TEXT);
  209. $stmt->bindValue(':headers_json', json_encode($headers), SQLITE3_TEXT);
  210. $stmt->execute();
  211. return $this->db->lastInsertRowID();
  212. }
  213. public function session_get(){
  214. $count = $this->session_count();
  215. if($count === 0){
  216. return false;
  217. }
  218. $offset = apcu_inc("d." . $this->scraper) % $count;
  219. $stmt = $this->db->prepare(
  220. "SELECT * " .
  221. "FROM sessions " .
  222. "ORDER BY id ASC " .
  223. "LIMIT 1 OFFSET :offset"
  224. );
  225. $stmt->bindValue(':offset', $offset, SQLITE3_INTEGER);
  226. $result = $stmt->execute();
  227. $row = $result->fetchArray(SQLITE3_ASSOC);
  228. return [
  229. "id" => $row["id"],
  230. "created_at" => $row["created_at"],
  231. "proxy" => $row["proxy"],
  232. "headers" => json_decode($row["headers_json"], true)
  233. ];
  234. }
  235. public function session_destroy_list(){
  236. if(
  237. !preg_match(
  238. '/^[A-Za-z0-9-]+$/',
  239. $this->scraper
  240. )
  241. ){
  242. // path traversal
  243. return false;
  244. }
  245. if(
  246. file_exists("{$this->db_path_prefix}{$this->scraper}.sqlite") &&
  247. is_file("{$this->db_path_prefix}{$this->scraper}.sqlite")
  248. ){
  249. unlink("{$this->db_path_prefix}{$this->scraper}.sqlite");
  250. return true;
  251. }
  252. return false;
  253. }
  254. }