am_dot_com

ACA 20201203

Dec 3rd, 2020 (edited)
124
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 15.03 KB | None | 0 0
  1. <?php
  2.  
  3. /*
  4.  * testing with
  5.  * https://boards.4chan.org/wg/
  6.  * https://boards.4chan.org/wg/2
  7.  * ...
  8.  * https://boards.4chan.org/wg/10
  9.  * 404 for .../1 and for URLs ending in numbers >10
  10.  */
  11.  
  12. require_once "AmUtil.php";
  13.  
  14. class FourChanBot {
  15.     //data member
  16.     private $mBoardName; //e.g. "wg"
  17.     private $mBoardValidUrls; //"e.g. ["https://.../wg/" ... "https://.../wg/10"]
  18.     private $mBoardHtmlForValidUrls; //e.g. [
  19.     //"https://.../wg/" => "<html>...</html>"
  20.     // , ...
  21.     //"https://.../wg/10" => "<html>... <a href="i1.jpg">...</a></html>"
  22.     //]
  23.     private $mHyperlinksInBoardValidUrls;
  24.     /*
  25.      * [ ..."https://.../wg/10" => [["anchor"=>"??", "href"=>"https:..."], ...]
  26.      */
  27.  
  28.     private $mDownloadFolder;
  29.  
  30.     public function getDownloadFolder(){
  31.         //"./DLS/wg/"
  32.         return $this->mDownloadFolder.$this->mBoardName."\\";
  33.     }//getDownloadFolder
  34.  
  35.     private function buildHyperlinksForBoardValidUrlsForImages(){
  36.         foreach ($this->mBoardHtmlForValidUrls as $url=>$html){
  37.             echo "current URL: $url".PHP_EOL;
  38.  
  39.             $as = AmUtil::extractHyperlinksFromHtmlSourceCode($html);
  40.             //var_dump ($as);
  41.  
  42.             $asForImages = AmUtil::filterHyperlinksKeepingOnlyThoseWithHrefsEndingIn(
  43.                 $as,
  44.                 AmUtil::IMAGE_FILTERS
  45.             );
  46.             var_dump($asForImages);
  47.  
  48.             //$this->mHyperlinksInBoardValidUrls[$url] = $as;
  49.             $this->mHyperlinksInBoardValidUrls[$url] = $asForImages;
  50.         }//foreach
  51.         return $this->mHyperlinksInBoardValidUrls;
  52.     }//buildHyperlinksForBoardValidUrlsForImages
  53.  
  54.     public function downloadImages(
  55.         $paPairsAnchorHrefForImages = null //php 7+ will thrown an error if argument is missing
  56.     ){
  57.         $paPairsAnchorHrefForImages =
  58.             //if no argument is received
  59.             empty($paPairsAnchorHrefForImages) ?
  60.             //set the parameter value to the pairs anchor/href of images
  61.             //found in the board's first page ONLY
  62.             $this->mHyperlinksInBoardValidUrls[
  63.                 $this->mBoardValidUrls[0] //URL of board's first page
  64.             ]
  65.             :
  66.             //if an argument was sent, respect it, use it
  67.             $paPairsAnchorHrefForImages;
  68.  
  69.         $aRet = [];
  70.         for($idx=0; $idx<count($paPairsAnchorHrefForImages); $idx++){
  71.             $aCurrentPair = $paPairsAnchorHrefForImages[$idx];
  72.             $iBytesWrittenOrFalse = $this->download(
  73.                 $aCurrentPair
  74.             );
  75.  
  76.             $aRet[] = $iBytesWrittenOrFalse;
  77.         }//for
  78.         return $aRet;
  79.     }//downloadImages
  80.  
  81.     public function download(
  82.         $paAnchorHref
  83.     ){
  84.         $bCheck = $paAnchorHref!=null
  85.             && is_array($paAnchorHref)
  86.             && array_key_exists(AmUtil::KEY_HREF, $paAnchorHref)
  87.             && array_key_exists(AmUtil::KEY_ANCHOR, $paAnchorHref);
  88.  
  89.         if ($bCheck){
  90.             $strAnchor = $paAnchorHref[AmUtil::KEY_ANCHOR];
  91.             $strHref = $paAnchorHref[AmUtil::KEY_HREF];
  92.             $iBytesForTheImage = AmUtil::consumeUrl($strHref);
  93.  
  94.             $strFileName = $strAnchor."_".$strHref;
  95.             $strFileNameSanitized = AmUtil::sanitizeStringForFileSystem(
  96.                 $strFileName
  97.             );
  98.  
  99.             $strDownloadFolder = $this->getDownloadFolder();
  100.             if (!file_exists($strDownloadFolder)){
  101.                 $bTrueOnSuccessFalseOnFailure =
  102.                     mkdir(
  103.                         $strDownloadFolder,
  104.                         true
  105.                     );
  106.  
  107.                 $strDownloadFolder = $bTrueOnSuccessFalseOnFailure ?
  108.                     $strDownloadFolder
  109.                     :
  110.                     "./";
  111.             }
  112.  
  113.  
  114.  
  115.             $strFullPathForTheDownload =
  116.                 $strDownloadFolder.$strFileNameSanitized;
  117.  
  118.             $iBytesWrittenOrFalse =
  119.                 file_put_contents(
  120.                     //./DLS/1496495232742_https___i.4cdn.org_wg_1605824410734.png"
  121.                     $strFullPathForTheDownload,
  122.                     $iBytesForTheImage,
  123.                     LOCK_EX
  124.                 );
  125.             return $iBytesWrittenOrFalse;
  126.         }//if
  127.  
  128.         return false;
  129.     }//download
  130.  
  131.     const DEFAULT_DOWNLOAD_FOLDER = ".\\DLS\\";
  132.     public function __construct(
  133.         string $pStrBoardName,
  134.         string $pStrDownloadFolder = self::DEFAULT_DOWNLOAD_FOLDER
  135.     )
  136.     {
  137.         $this->mDownloadFolder = $pStrDownloadFolder;
  138.         $this->mBoardName = $pStrBoardName;
  139.  
  140.         //this costs no time
  141.         $this->mBoardValidUrls =
  142.             $this->buildAllValidBoardUrls();
  143.  
  144.         //this can take time: it will consume each and all of the board's pages
  145.         //$this->mBoardHtmlForValidUrls = $this->buildHtmlOfAllBoardPages(); //method returns null
  146.         $this->buildHtmlOfAllBoardPages(); //method returns null, but it built the data member with the proper values
  147.  
  148.         $this->buildHyperlinksForBoardValidUrlsForImages();
  149.     }//__construct
  150.  
  151.     const BASE_URL = "https://boards.4chan.org";
  152.     const MIN_PAGE = 1;
  153.     const MAX_PAGE = 10;
  154.     public function buildAllValidBoardUrls() : array
  155.     {
  156.         $aRet = [];
  157.  
  158.         for(
  159.             $iPage=self::MIN_PAGE;
  160.             $iPage<=self::MAX_PAGE;
  161.             $iPage++
  162.         ){
  163.             $strUrl = sprintf(
  164.                 "%s/%s/%s",
  165.                 self::BASE_URL,
  166.                 $this->mBoardName,
  167.                 $iPage===1 ? "" : $iPage
  168.             );
  169.             $aRet[] = $strUrl;
  170.         }//for
  171.  
  172.         return $aRet;
  173.     }//buildAllValidBoardUrls
  174.  
  175.     public function buildHtmlOfAllBoardPages(){
  176.         foreach($this->mBoardValidUrls as $strOneValidUrl){
  177.             $strHtml = AmUtil::consumeUrl($strOneValidUrl);
  178.  
  179.             $this->mBoardHtmlForValidUrls[$strOneValidUrl] =
  180.                 $strHtml;
  181.             /*
  182.             [
  183.                 //0 => "<html>...</html>"
  184.                 "https://boards.4chan.org/wg/2" => "<html>...</html>"
  185.             ];
  186.             */
  187.         }//forearch
  188.  
  189.         //return $this->mBoardHtmlForValidUrls;
  190.     }//buildHtmlOfAllBoardPages
  191.  
  192.     public function getMBoardName()
  193.     {
  194.         return $this->mBoardName;
  195.     }
  196.  
  197.     public function getMBoardValidUrls()
  198.     {
  199.         return $this->mBoardValidUrls;
  200.     }
  201.  
  202.     public function getMBoardHtmlForValidUrls()
  203.     {
  204.         return $this->mBoardHtmlForValidUrls;
  205.     }
  206.  
  207.     /*
  208.      * now that we have tools to associate URLs to corresponding HTML
  209.      * we are in need of solutions to parse the HTML and extract hyperlinks
  210.      * from it
  211.      */
  212. }//FourChanBot
  213.  
  214.  
  215. //**
  216.  
  217. <?php
  218.  
  219. class AmUtil{
  220.     const IMPOSSIBLE_MONTH = -1;
  221.     const BOT_SIGNATURE = "For educational tests only";
  222.  
  223.     public static function leapYear(
  224.         $pY
  225.     ){
  226.         return ($pY%400 === 0) || ($pY%4===0 && ($pY%100!==0));
  227.     }//leapYear
  228.  
  229.     public static function numberOfDaysInMonth(
  230.         $pY,
  231.         $pM
  232.     ){
  233.         switch($pM){
  234.             case 1: case 3:case 5:case 7:case 8: case 10;case 12: return 31;
  235.             case 4: case 6:case 9:case 11: return 30;
  236.             case 2: return (self::leapYear($pY) ? 29 :  28);
  237.             default: return self::IMPOSSIBLE_MONTH;
  238.         }//switch
  239.     }//numberOfDaysInMonth
  240.  
  241.     public static function consumeUrl(
  242.         $pUrl //can be an HTML page, can be a JPG, ...
  243.     ){
  244.         //$bValid = is_string($pUrl) && strlen($pUrl);
  245.         $ch = curl_init($pUrl);
  246.         if ($ch){
  247.             //curl_setopt(CURLOPT_URL, $pUrl);
  248.             /*
  249.              * makes it explic that the request
  250.              * will happen using HTTP GET
  251.              */
  252.             curl_setopt(
  253.                 $ch,
  254.                 CURLOPT_HTTPGET,
  255.                 true
  256.             );
  257.  
  258.             /*
  259.              * disables the verification of SSL
  260.              * certificates
  261.              * useful when not using cacert.pem
  262.              */
  263.             curl_setopt(
  264.                 $ch,
  265.                 CURLOPT_SSL_VERIFYPEER,
  266.                 true
  267.             );
  268.  
  269.             /*
  270.              * sets a user agent string for our
  271.              * software
  272.              */
  273.             curl_setopt(
  274.                 $ch,
  275.                 CURLOPT_USERAGENT,
  276.                 self::BOT_SIGNATURE
  277.             );
  278.  
  279.             //if set to true, curl_exec will return
  280.             //the data consumed at the URL
  281.             //instead of just true/false
  282.             curl_setopt(
  283.                 $ch,
  284.                 CURLOPT_RETURNTRANSFER,
  285.                 true
  286.             );
  287.  
  288.             /*
  289.              * makes it clear that we want all the bytes
  290.              */
  291.             curl_setopt(
  292.                 $ch,
  293.                 CURLOPT_BINARYTRANSFER, //deprecated
  294.                 true
  295.             );
  296.  
  297.             /*
  298.              * sets automatic handling of the encoded
  299.              * data
  300.              */
  301.             curl_setopt(
  302.                 $ch,
  303.                 CURLOPT_ENCODING,
  304.                 ""
  305.             );
  306.  
  307.             $bin = curl_exec($ch);
  308.  
  309.             return $bin;
  310.         }//if
  311.         return false;
  312.     }//consumeUrl
  313.  
  314.     /*
  315.      * receives HTML source code
  316.      * returns a collection of all "a" elements found,
  317.      * structured as pairs "anchor", "href"
  318.      *
  319.      * E.g.
  320.      * if this is the input:
  321.      * <html><body><a href="URL1">anchor1</a></body></html>"
  322.      * the output should be:
  323.      * [
  324.      *  [ "anchor" => "anchor1", "href" => "URL1"]
  325.      * ]
  326.      */
  327.     const KEY_HREF = "HREF";
  328.     const KEY_ANCHOR = "ANCHOR";
  329.     public static function extractHyperlinksFromHtmlSourceCode(
  330.         string $pStrHtmlSourceCode
  331.     ) /*: array */
  332.     {
  333.         $aRet = []; //the collection of all "a" elements found
  334.         $oDom = new DOMDocument();
  335.         if ($oDom){
  336.             //@ - "silencer"
  337.             @$oDom->loadHTML($pStrHtmlSourceCode);
  338.             /*
  339.              * array of "a" elements
  340.              */
  341.             $as = $oDom->getElementsByTagName('a');
  342.  
  343.             //foreach ($col as $indexOfElement => $valueOfElement){body}
  344.             //foreach ($col as $valueOfElement){body}
  345.             foreach ($as as $someAElement){
  346.                 $strAnchor = trim($someAElement->nodeValue);
  347.                 $strHref = trim($someAElement->getAttribute('href'));
  348.  
  349.                 $aPair = [
  350.                     self::KEY_HREF => $strHref,
  351.                     self::KEY_ANCHOR => $strAnchor
  352.                 ];
  353.  
  354.                 $aRet[] = $aPair;
  355.             }//foreach
  356.         }//if
  357.         return $aRet;
  358.     }//extractHyperlinksFromHtmlSourceCode
  359.  
  360.     //**
  361.     /*
  362.      * tool to filter Hyperlinks,
  363.      * keeping only those with certain href endings
  364.      * e.g.
  365.      * input [
  366.          * ["anchor"=>?, "href"=>".xpto"],
  367.          * ["anchor"=>"pic", "href"=>"bla.jpg"]
  368.      * ]
  369.      *
  370.      *
  371.      */
  372.     const IMAGE_FILTERS = [
  373.         ".jpg", ".png", ".jp2", ".gif",
  374.         ".gifv", ".bmp", ".svg"
  375.     ];
  376.     public static function
  377. filterHyperlinksKeepingOnlyThoseWithHrefsEndingIn(
  378.         $paHyperlinksAsPairsAnchorsHref,
  379.         $paFilters = [], //no filters, by default!
  380.         $pStrURLPrefixIfSchemaIsMissing = "https:"
  381.     )
  382.     {
  383.         $aRet = [];
  384.         $hrefs = []; //2020-12-03
  385.  
  386.         $bShouldDoNothing =
  387.             is_array($paFilters) && count($paFilters)===0;
  388.  
  389.         if ($bShouldDoNothing)
  390.             return $paHyperlinksAsPairsAnchorsHref;
  391.  
  392.         //if there are filters
  393.         foreach (
  394.             $paHyperlinksAsPairsAnchorsHref
  395.             as
  396.             $aPair
  397.         ){
  398.             $strAnchor = $aPair[self::KEY_ANCHOR];
  399.             $strHref = $aPair[self::KEY_HREF];
  400.  
  401.             $bHrefEndsInAtLeastOneOfTheFilters =
  402.                 self::stringEndsInOneOfTheFollowing(
  403.                     $strHref,
  404.                     $paFilters
  405.                 );
  406.  
  407.             if ($bHrefEndsInAtLeastOneOfTheFilters){
  408.                 $bUrlIsMissingSchema = stripos(
  409.                     $strHref, "//"
  410.                 ) === 0;
  411.                 if ($bUrlIsMissingSchema){
  412.                     $strHref =
  413.                         "$pStrURLPrefixIfSchemaIsMissing$strHref";
  414.  
  415.                     $aPair[self::KEY_HREF] = $strHref;
  416.                 }
  417.  
  418.                 $bHrefFoundAlreadyExistsInCollectionOfHrefs =
  419.                     array_search(
  420.                         $strHref,
  421.                         $hrefs
  422.                     ) !== false;
  423.  
  424.                 $bNewHref = !$bHrefFoundAlreadyExistsInCollectionOfHrefs;
  425.                 if ($bNewHref){
  426.                     $hrefs[] = $strHref; //conditional entry (depends on being new)
  427.                     $aRet[] = $aPair; //conditional entry (depends on being new)
  428.                 }//if
  429.                 else{
  430.                     //feedback for repeat rejects
  431.                 }
  432.             }//if
  433.         }//foreach
  434.  
  435.         return $aRet;
  436.     }//filterHyperlinksKeepingOnlyThoseWithHrefsEndingIn
  437.  
  438.     /*
  439.      * stringEndsInOneOfTheFollowing ("Artur", ["ab", "r"]) => true
  440.      * stringEndsInOneOfTheFollowing ("pic.png", [".png", "jpg"]) => true
  441.      * case INSENSITIVE!
  442.      */
  443.     public static function stringEndsInOneOfTheFollowing(
  444.         string $pStr,
  445.         array $paTerminations,
  446.         bool $pbCaseInsensitive = true
  447.     ){
  448.         foreach($paTerminations as $someTermination){
  449.             if ($pbCaseInsensitive){
  450.                 $iWhereDoesTheTerminationOccur =
  451.                     strripos($pStr, $someTermination);
  452.             }//if
  453.             else{
  454.                 $iWhereDoesTheTerminationOccur =
  455.                     strrpos($pStr, $someTermination);
  456.             }//else
  457.  
  458.             $bTerminationOccurs =
  459.                 $iWhereDoesTheTerminationOccur!==false;
  460.  
  461.             if ($bTerminationOccurs){
  462.                 //it it exactly at the END of the string?
  463.                 $bExactlyAtTheEnd =
  464.                     strlen($pStr) ===
  465.                         $iWhereDoesTheTerminationOccur +
  466.                         strlen($someTermination);
  467.                 if ($bExactlyAtTheEnd) return true;
  468.             }//if
  469.         }//foreach
  470.         return false;
  471.     }//stringEndsInOneOfTheFollowing
  472.  
  473.     const INVALID_FILENAME_SYMBOLS =
  474.         [":", "/", "\\", "*", "?", "<", ">", "|", "\""];
  475.  
  476.     public static function sanitizeStringForFileSystem(
  477.         $pStrToSanitize,
  478.         $pStrReplacementSymbol = "_"
  479.     ){
  480.         foreach (self::INVALID_FILENAME_SYMBOLS as $strReplaceThis){
  481.             $pStrToSanitize = str_replace(
  482.                 $strReplaceThis,
  483.                 $pStrReplacementSymbol,
  484.                 $pStrToSanitize
  485.             );
  486.         }//foreach
  487.  
  488.         return $pStrToSanitize;
  489.     }//sanitizeStringForFileSystem
  490. }//AmUtil
  491.  
  492. //**
  493.  
  494. <?php
  495. require_once "FourChanBot.php";
  496.  
  497. $bot = new FourChanBot("wg");
  498. $bot->downloadImages(); //no argument! Defaults will apply.
  499. //$bot->consumePage(1);
  500. //$bot->downloadResourcesAtPage(1);
  501. //$allValidUrls = $bot->buildAllValidBoardUrls();
  502. //var_dump($allValidUrls);
  503.  
  504. //var_dump($bot->getMBoardHtmlForValidUrls());
  505.  
  506. //**
  507.  
  508. <?php
  509. mkdir ("./test2");
  510.  
Advertisement
Add Comment
Please, Sign In to add comment