🎉 Hello world
This commit is contained in:
		
							
								
								
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| /.idea | ||||
| /main.php | ||||
| /test.jpg | ||||
| /pool/* | ||||
| !/pool/.gitkeep | ||||
| /vendor | ||||
| /config.json | ||||
							
								
								
									
										21
									
								
								composer.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								composer.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| { | ||||
|     "name": "shikiryu/webgobbler", | ||||
|     "description": "WebGobbler in PHP", | ||||
|     "type": "project", | ||||
|     "authors": [ | ||||
|         { | ||||
|             "name": "Clément", | ||||
|             "email": "clement@desmidt.fr" | ||||
|         } | ||||
|     ], | ||||
|     "require": { | ||||
|       "fabpot/goutte": "^4.0", | ||||
|       "ext-imagick": "*" | ||||
|     }, | ||||
|   "autoload": { | ||||
|     "psr-4": { | ||||
|       "Shikiryu\\WebGobbler\\": "src/", | ||||
|       "Shikiryu\\WebGobbler\\Collector\\": "src/Collector" | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										1348
									
								
								composer.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										1348
									
								
								composer.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										16
									
								
								config.json.template
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								config.json.template
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| { | ||||
|   "collectors": [], | ||||
|   "pool": { | ||||
|     "directory": "/home/user/webcollage/pool", | ||||
|     "nb_images": 10 | ||||
|   }, | ||||
|   "assembler": { | ||||
|     "sizex": 2000, | ||||
|     "sizey": 2000, | ||||
|     "mirror": false, | ||||
|     "emboss": true, | ||||
|     "invert": false, | ||||
|     "nbx": 5, | ||||
|     "nby": 5 | ||||
|   } | ||||
| } | ||||
							
								
								
									
										0
									
								
								pool/.gitkeep
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								pool/.gitkeep
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										37
									
								
								src/Assembler.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/Assembler.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| <?php | ||||
|  | ||||
| namespace Shikiryu\WebGobbler; | ||||
|  | ||||
| abstract class Assembler | ||||
| { | ||||
|     /** | ||||
|      * @var \Shikiryu\WebGobbler\Pool | ||||
|      */ | ||||
|     protected $pool; | ||||
|  | ||||
|     /** | ||||
|      * @var \Shikiryu\WebGobbler\Config | ||||
|      */ | ||||
|     protected $config; | ||||
|  | ||||
|     /** | ||||
|      * Assembler constructor. | ||||
|      * | ||||
|      * @param \Shikiryu\WebGobbler\Pool   $pool | ||||
|      * @param \Shikiryu\WebGobbler\Config $config | ||||
|      */ | ||||
|     public function __construct(Pool $pool, Config $config) | ||||
|     { | ||||
|         $this->pool = $pool; | ||||
|         $this->config = $config; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param string $file | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     abstract public function saveTo($file); | ||||
|  | ||||
|     abstract public function display(); | ||||
| } | ||||
							
								
								
									
										48
									
								
								src/Assembler/Mosaic.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/Assembler/Mosaic.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| <?php | ||||
|  | ||||
| namespace Shikiryu\WebGobbler\Assembler; | ||||
|  | ||||
| use Shikiryu\WebGobbler\Assembler; | ||||
|  | ||||
| class Mosaic extends Assembler | ||||
| { | ||||
|     /** | ||||
|      * @param string $file | ||||
|      * | ||||
|      * @return void | ||||
|      * @throws \ImagickException | ||||
|      */ | ||||
|     public function saveTo($file) | ||||
|     { | ||||
|         $final_image = new \Imagick(); | ||||
|         $final_image->setColorspace(\Imagick::COLORSPACE_RGB); | ||||
|         $final_image->newImage($this->config->get('assembler.sizex'), $this->config->get('assembler.sizey'), 'none'); | ||||
|         $image_size_x = $this->config->get('assembler.sizex') / $this->config->get('assembler.nbx'); | ||||
|         $image_size_y = $this->config->get('assembler.sizey') / $this->config->get('assembler.nby'); | ||||
|         for ($y = 0; $y < $this->config->get('assembler.nby'); $y++) { | ||||
|             for ($x = 0; $x < $this->config->get('assembler.nbx'); $x++) { | ||||
|                 $image = new \Imagick($this->pool->getImage()); | ||||
|                 if ($image->getColorspace() !== \Imagick::COLORSPACE_RGB) { | ||||
|                     $image->setColorspace(\Imagick::COLORSPACE_RGB); | ||||
|                 } | ||||
|                 $image->scaleImage($image_size_x, $image_size_y, true); | ||||
|                 $final_image->compositeImage($image, \Imagick::COMPOSITE_DEFAULT, $x * $image_size_x, $y * $image_size_y); | ||||
|             } | ||||
|         } | ||||
|         if (true === $this->config->get('assembler.mirror')) { | ||||
|             $final_image->flopImage(); | ||||
|         } | ||||
|         if (true === $this->config->get('assembler.emboss')) { | ||||
|             $final_image->embossImage(0, 1); | ||||
|         } | ||||
|         if (true === $this->config->get('assembler.invert')) { | ||||
|             $final_image->negateImage(false); | ||||
|         } | ||||
|         $final_image->writeImage($file); | ||||
|     } | ||||
|  | ||||
|     public function display() | ||||
|     { | ||||
|         // TODO: Implement display() method. | ||||
|     } | ||||
| } | ||||
							
								
								
									
										44
									
								
								src/Assembler/Simple.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/Assembler/Simple.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| <?php | ||||
|  | ||||
| namespace Shikiryu\WebGobbler\Assembler; | ||||
|  | ||||
| use Shikiryu\WebGobbler\Assembler; | ||||
|  | ||||
| class Simple extends Assembler | ||||
| { | ||||
|     /** | ||||
|      * @param string $file | ||||
|      */ | ||||
|     public function saveTo($file) | ||||
|     { | ||||
|         try { | ||||
|             $image = new \Imagick($this->pool->getImage()); | ||||
|             if ($image->getColorspace() !== \Imagick::COLORSPACE_RGB) { | ||||
|                 $image->setColorspace(\Imagick::COLORSPACE_RGB); | ||||
|             } | ||||
|             $size = $image->getSize(); | ||||
|             $imagex = $size['columns']; | ||||
|             $imagey = $size['rows']; | ||||
|             if ($imagex !== $this->config->get('assembler.sizex') || $imagey !== $this->config->get('assembler.sizey')) { | ||||
|                 $image->thumbnailImage($this->config->get('assembler.sizex'), $this->config->get('assembler.sizey')); | ||||
|             } | ||||
|             if (true === $this->config->get('assembler.mirror')) { | ||||
|                 $image->flopImage(); | ||||
|             } | ||||
|             if (true === $this->config->get('assembler.emboss')) { | ||||
|                 $image->embossImage(0, 1); | ||||
|             } | ||||
|             if (true === $this->config->get('assembler.invert')) { | ||||
|                 $image->flipImage(); | ||||
|             } | ||||
|             $image->writeImage($file); | ||||
|         } catch (\ImagickException $e) { | ||||
|             echo $e->getMessage(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public function display() | ||||
|     { | ||||
|         // TODO: Implement display() method. | ||||
|     } | ||||
| } | ||||
							
								
								
									
										177
									
								
								src/Assembler/Superpose.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								src/Assembler/Superpose.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,177 @@ | ||||
| <?php | ||||
|  | ||||
| namespace Shikiryu\WebGobbler\Assembler; | ||||
|  | ||||
| use Imagick; | ||||
| use Shikiryu\WebGobbler\Assembler; | ||||
|  | ||||
| class Superpose extends Assembler | ||||
| { | ||||
|     /** | ||||
|      * @var Imagick | ||||
|      */ | ||||
|     private $current_image; | ||||
|  | ||||
|     /** | ||||
|      * @param string $file | ||||
|      * | ||||
|      * @return void | ||||
|      * @throws \ImagickException | ||||
|      * @throws \Exception | ||||
|      */ | ||||
|     public function saveTo($file) | ||||
|     { | ||||
|         $image_to_superpose = new \Imagick($this->pool->getImage()); | ||||
|         $image_x = $image_to_superpose->getImageWidth(); | ||||
|         $image_y = $image_to_superpose->getImageHeight(); | ||||
|         if ($image_x < 32 || $image_y < 32) { | ||||
|             throw new \Exception('Image too small.'); | ||||
|         } | ||||
|  | ||||
|         $this->current_image = $image_to_superpose; | ||||
|  | ||||
|         $nb_images = $this->config->get('assembler.superpose.min_num_images', 5); | ||||
|         for ($i = 0; $i < $nb_images; $i++) { | ||||
|             $this->current_image = $this->superpose(); | ||||
|         } | ||||
|         $this->current_image->writeImage($file); | ||||
|     } | ||||
|  | ||||
|     public function superpose() | ||||
|     { | ||||
|         return $this->superposeOneImage($this->current_image, new \Imagick($this->pool->getImage())); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Superposes one image in the current image. | ||||
|      * This method must only be called by the assembler_superpose thread ! | ||||
|      * | ||||
|      * @param \Imagick $current_image | ||||
|      * @param \Imagick $image_to_superpose | ||||
|      * | ||||
|      * @return \Imagick | ||||
|      * @throws \Exception | ||||
|      */ | ||||
|     private function superposeOneImage($current_image, $image_to_superpose) | ||||
|     { | ||||
|         if ($this->config->get('assembler.superpose.variante', 0) === 1) { | ||||
|             $current_image->brightnessContrastImage(0.99, 1); | ||||
|         } | ||||
|         if ($image_to_superpose->getColorspace() !== \Imagick::COLORSPACE_RGB) { | ||||
|             $image_to_superpose->setColorspace(\Imagick::COLORSPACE_RGB); | ||||
|         } | ||||
|  | ||||
|         # If the image is bigger than current image, scale it down to 1/2 of final picture dimensions | ||||
|         # (while keeping its ratio) | ||||
|         $image_x = $image_to_superpose->getImageWidth(); | ||||
|         $image_y = $image_to_superpose->getImageHeight(); | ||||
|         if ($image_x > $this->config->get('assembler.sizex') || $image_y > $this->config->get('assembler.sizey')) { | ||||
|             try { | ||||
|                 $image_to_superpose->scaleImage($this->config->get('assembler.sizex') / 2, $this->config->get('assembler.sizey') / 2, true); | ||||
|             } catch (\ImagickException $e) { | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         # Scale down/up image if required. | ||||
|         /*scaleValue = self.CONFIG["assembler.superpose.scale"] | ||||
|         if str(scaleValue) != "1.0": | ||||
|             try: | ||||
|                 imageToSuperpose.thumbnail((int(float(imagex)*scaleValue),int(float(imagey)*scaleValue)),Image.ANTIALIAS) | ||||
|             except TypeError:  #TypeError: unsubscriptable object  ; Spurious exception in PIL.  :-( | ||||
|                 raise BadImage | ||||
|         (imagex,imagey) = imageToSuperpose.size*/ | ||||
|  | ||||
|         # Compensate for poorly-contrasted images on the web | ||||
|         /*try: | ||||
|             imageToSuperpose = ImageOps.autocontrast(imageToSuperpose) | ||||
|         except TypeError:  # Aaron tells me that this exception occurs with PNG images. | ||||
|             raise BadImage*/ | ||||
|  | ||||
|         # Some image are too white. | ||||
|         # For example, the photo of a coin on a white background. | ||||
|         # These picture degrad the quality of the final image. | ||||
|         # We try to dectect them by summing the value of the pixels | ||||
|         # on the borders. | ||||
|         # If the image is considered "white", we invert it. | ||||
|         /*pixelcount = 1  # 1 to prevent divide by zero error. | ||||
|         valuecount = 0 | ||||
|         try: | ||||
|             for x in range(0,imagex,20): | ||||
|                 (r,g,b) = imageToSuperpose.getpixel((x,5)) | ||||
|                 valuecount += r+g+b | ||||
|                 (r,g,b) = imageToSuperpose.getpixel((x,imagey-5)) | ||||
|                 valuecount += r+g+b | ||||
|                 pixelcount += 2 | ||||
|             for y in range(0,imagey,20): | ||||
|                 (r,g,b) = imageToSuperpose.getpixel((5,y)) | ||||
|                 valuecount += r+g+b | ||||
|                 (r,g,b) = imageToSuperpose.getpixel((imagex-5,y)) | ||||
|                 valuecount += r+g+b | ||||
|                 pixelcount += 2 | ||||
|         except TypeError:  #unsubscriptable object  Arrggghh... not again ! | ||||
|             raise BadImage   # Aggrrreeeuuuu... | ||||
|  | ||||
|         # If the average r+g+b of the border pixels exceed this value, | ||||
|         # we consider the image is too white, and we invert it. | ||||
|         if (100*(valuecount/(255*3))/pixelcount)>60:  # Cut at 60%.  (100% is RGB=(255,255,255)) | ||||
|             imageToSuperpose = ImageOps.invert(imageToSuperpose)*/ | ||||
|  | ||||
|         $paste_coords_x = random_int(-$image_x, $this->config->get('assembler.sizex')); | ||||
|         $paste_coords_y = random_int(-$image_y, $this->config->get('assembler.sizey')); | ||||
|  | ||||
|         # Darken image borders | ||||
|         $image_to_superpose = $this->darkenImageBorder($image_to_superpose, 30); | ||||
|  | ||||
|         if ($this->config->get('assembler.superpose.randomrotation', false)) { | ||||
|             $image_to_superpose->rotateImage('none', random_int(0, 359)); | ||||
|             $image_to_superpose = $this->darkenImageBorder($image_to_superpose, 30); | ||||
|         } | ||||
|  | ||||
| //        mask_image = ImageOps.autocontrast(imageToSuperpose.convert('L')) | ||||
|  | ||||
| //        if (self.CONFIG["assembler.superpose.variante"]==1) and (random.randint(0,100)<5):  # Invert the transparency of 5% of the images (Except if we are in variante 1 mode) | ||||
| //            mask_image = ImageOps.invert(mask_image) | ||||
|         $current_image->compositeImage($image_to_superpose, \Imagick::COMPOSITE_DEFAULT, $paste_coords_x, $paste_coords_y); | ||||
|  | ||||
|         if ($this->config->get('assembler.superpose.variante') === 0) { | ||||
|             $current_image->equalizeImage(); | ||||
|         } else { | ||||
|             $current_image->autoLevelImage(); | ||||
|         } | ||||
|  | ||||
|         return $current_image; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param \Imagick $image | ||||
|      * @param int|null     $border_size | ||||
|      * | ||||
|      * @return \Imagick | ||||
|      */ | ||||
|     private function darkenImageBorder(Imagick $image, $border_size = null) | ||||
|     { | ||||
|         if (null === $border_size) { | ||||
|             $border_size = $this->config->get('assembler.superpose.bordersmooth'); | ||||
|         } | ||||
|  | ||||
|         $image_x = $image->getImageWidth(); | ||||
|         $image_y = $image->getImageHeight(); | ||||
|  | ||||
|         for ($i = 0; $i < $border_size; $i++) { | ||||
|             $draw = new \ImagickDraw(); | ||||
|             $draw->setStrokeOpacity(($border_size-$i)/$border_size); | ||||
|             $draw->setStrokeWidth(1); | ||||
|             $draw->setStrokeColor('black'); | ||||
|             $draw->setFillColor('none'); | ||||
|             $draw->rectangle($i, $i, $image_x - $i, $image_y - $i); | ||||
|             $image->drawImage($draw); | ||||
|         } | ||||
|  | ||||
|         return $image; | ||||
|     } | ||||
|  | ||||
|     public function display() | ||||
|     { | ||||
|         // TODO: Implement display() method. | ||||
|     } | ||||
| } | ||||
							
								
								
									
										120
									
								
								src/Collector.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								src/Collector.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,120 @@ | ||||
| <?php | ||||
|  | ||||
| namespace Shikiryu\WebGobbler; | ||||
|  | ||||
| abstract class Collector | ||||
| { | ||||
|     /** | ||||
|      * Collector constructor. | ||||
|      * | ||||
|      * @param \Shikiryu\WebGobbler\Pool $pool | ||||
|      */ | ||||
|     public function __construct(Pool $pool) | ||||
|     { | ||||
|         $this->pool = $pool; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @return string | ||||
|      */ | ||||
|     abstract public function getName(); | ||||
|  | ||||
|     /** | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getPoolDirectory() | ||||
|     { | ||||
|         return sprintf('%s/%s', $this->pool->getPoolDirectory(), $this->getName()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @return int | ||||
|      */ | ||||
|     abstract public function getRandomImage(); | ||||
|  | ||||
|     /** | ||||
|      * @param int $number | ||||
|      * | ||||
|      * @return int | ||||
|      */ | ||||
|     abstract public function getRandomImages(int $number); | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Generates a random word. | ||||
|      * This method can be used by all derived classes. | ||||
|      * Usefull to get random result from search engines when you do not have | ||||
|      * a dictionnary at hand. | ||||
|      * The generated word can be a number (containing only digits), | ||||
|      * a word (containing only letters) or both mixed. | ||||
|      * Output: string (a random word) | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     protected function generateRandomWord() | ||||
|     { | ||||
|         $word = '1'; | ||||
|         try { | ||||
|             if (random_int(0, 100) < 30) { // Sometimes use only digits | ||||
|                 if (random_int(0, 100) < 30) { | ||||
|                     $word = random_int(1, 999); | ||||
|                 } else { | ||||
|                     $word = random_int(1, 999999); | ||||
|                 } | ||||
|             } else { // Generate a word containing letters | ||||
|                 $word = ''; | ||||
|                 $charset = 'abcdefghijklmnopqrstuvwxyz'; // Search for random word containing letter only. | ||||
|                 if (random_int(0, 100) < 60) { // Sometimes include digits with letters | ||||
|                     $charset = 'abcdefghijklmnopqrstuvwxyz' . 'abcdefghijklmnopqrstuvwxyz' . '0123456789';  // *2 to have more letters than digits | ||||
|                 } | ||||
|                 $charset = str_split($charset); | ||||
|                 for ($i = 0, $l = random_int(2, 5); $i <= $l; $i++) { //  Only generate short words (2 to 5 characters) | ||||
|                     $word .= $charset[array_rand($charset)]; | ||||
|                 } | ||||
|             } | ||||
|         } catch (\Exception $e) { | ||||
|         } | ||||
|  | ||||
|         return $word; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param array $parts | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     protected function reverse_url(array $parts) { | ||||
|         if (array_key_exists('query', $parts) && is_array($parts['query'])) { | ||||
|             $parts['query'] = http_build_query($parts['query']); | ||||
|         } | ||||
|         return (isset($parts['scheme']) ? "{$parts['scheme']}:" : '') . | ||||
|             ((isset($parts['user']) || isset($parts['host'])) ? '//' : '') . | ||||
|             (isset($parts['user']) ? "{$parts['user']}" : '') . | ||||
|             (isset($parts['pass']) ? ":{$parts['pass']}" : '') . | ||||
|             (isset($parts['user']) ? '@' : '') . | ||||
|             (isset($parts['host']) ? "{$parts['host']}" : '') . | ||||
|             (isset($parts['port']) ? ":{$parts['port']}" : '') . | ||||
|             (isset($parts['path']) ? "{$parts['path']}" : '') . | ||||
|             (isset($parts['query']) ? "?{$parts['query']}" : '') . | ||||
|             (isset($parts['fragment']) ? "#{$parts['fragment']}" : ''); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param \Shikiryu\WebGobbler\Pool $pool | ||||
|      * | ||||
|      * @return \Shikiryu\WebGobbler\Collector[] | ||||
|      */ | ||||
|     public static function getAllCollectors(Pool $pool) | ||||
|     { | ||||
|         $collectors = []; | ||||
|         foreach (glob(__DIR__ . '/Collector/*.php') as $class) { | ||||
|             $classname = sprintf('Shikiryu\\WebGobbler\\Collector\\%s', basename($class, '.php')); | ||||
|             $tmp_class = new $classname($pool); | ||||
|             if (is_subclass_of($tmp_class, __CLASS__)) { | ||||
|                 $collectors[] = $tmp_class; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return $collectors; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										65
									
								
								src/Collector/DeviantartCollector.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/Collector/DeviantartCollector.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | ||||
| <?php | ||||
|  | ||||
| namespace Shikiryu\WebGobbler\Collector; | ||||
|  | ||||
| use Goutte\Client; | ||||
| use Shikiryu\WebGobbler\Collector; | ||||
|  | ||||
| class DeviantartCollector extends Collector | ||||
| { | ||||
|     /** | ||||
|      * | ||||
|      */ | ||||
|     public const RANDOM_URL = 'https://www.deviantart.com/popular/deviations'; | ||||
|     /** | ||||
|      * Regular expression to extract the maximum deviantionID from homepage | ||||
|      */ | ||||
|     public const RE_ALLDEVIATIONID = '/href="https:\/\/www.deviantart.com\/[^\/]+\/art\/([^"]+)"/m'; | ||||
|  | ||||
|     /** | ||||
|      * @return int | ||||
|      */ | ||||
|     public function getRandomImage() | ||||
|     { | ||||
|         return $this->getRandomImages(1); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param int $number | ||||
|      * | ||||
|      * @return int | ||||
|      */ | ||||
|     public function getRandomImages(int $number = 1) | ||||
|     { | ||||
|         $html = file_get_contents(self::RANDOM_URL); | ||||
|         preg_match_all(self::RE_ALLDEVIATIONID, $html, $deviant_ids); | ||||
|         $deviant_ids = array_map(static function ($deviant_id) { | ||||
|             $array = explode('-', $deviant_id); | ||||
|  | ||||
|             return (int)end($array); | ||||
|         }, $deviant_ids[1]); | ||||
|         $deviant_ids = array_unique($deviant_ids); | ||||
|  | ||||
|         $index_to_download = array_rand($deviant_ids, $number); | ||||
|         if (!is_array($index_to_download)) { | ||||
|             $index_to_download = [$index_to_download]; | ||||
|         } | ||||
|  | ||||
|         foreach ($index_to_download as $deviant_id) { | ||||
|             $client = new Client(); | ||||
|             $crawler = $client->request('GET', 'https://www.deviantart.com/deviation/'.$deviant_ids[$deviant_id]); | ||||
|             $img_url = $crawler->filter('[data-hook="art_stage"] img')->eq(0)->attr('src'); | ||||
|             file_put_contents($this->getPoolDirectory() . '/' . basename(parse_url($img_url, PHP_URL_PATH)), file_get_contents($img_url)); | ||||
|         } | ||||
|  | ||||
|         return $number; // Fixme | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getName() | ||||
|     { | ||||
|         return 'collector_deviantart'; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										43
									
								
								src/Collector/LocalCollector.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/Collector/LocalCollector.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| <?php | ||||
|  | ||||
| namespace Shikiryu\WebGobbler\Collector; | ||||
|  | ||||
| use Shikiryu\WebGobbler\Collector; | ||||
|  | ||||
| class LocalCollector extends Collector | ||||
| { | ||||
|     /** | ||||
|      * @var string | ||||
|      */ | ||||
|     public $directory_to_scan = ''; | ||||
|     /** | ||||
|      * @var string[] | ||||
|      */ | ||||
|     public $filepaths = []; | ||||
|  | ||||
|     /** | ||||
|      * @return int|void | ||||
|      */ | ||||
|     public function getRandomImage() | ||||
|     { | ||||
|          | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getName() | ||||
|     { | ||||
|         return 'collector_local'; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param int $number | ||||
|      * | ||||
|      * @return int | ||||
|      */ | ||||
|     public function getRandomImages(int $number) | ||||
|     { | ||||
|         // TODO: Implement getRandomImages() method. | ||||
|     } | ||||
| } | ||||
							
								
								
									
										61
									
								
								src/Collector/YahooImageCollector.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/Collector/YahooImageCollector.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | ||||
| <?php | ||||
|  | ||||
| namespace Shikiryu\WebGobbler\Collector; | ||||
|  | ||||
| use Goutte\Client; | ||||
| use Shikiryu\WebGobbler\Collector; | ||||
|  | ||||
| class YahooImageCollector extends Collector | ||||
| { | ||||
|     public const SEARCH_URL = 'https://images.search.yahoo.com/search/images?p=%s'; | ||||
|  | ||||
|     /** | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getName() | ||||
|     { | ||||
|         return 'collector_yahooimage'; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @return int | ||||
|      */ | ||||
|     public function getRandomImage() | ||||
|     { | ||||
|         $word_to_search = $this->generateRandomWord(); | ||||
|  | ||||
|         $client = new Client(); | ||||
|         $crawler = $client->request('GET', sprintf(self::SEARCH_URL, $word_to_search)); | ||||
|         $imgs = $crawler->filter('noscript img'); | ||||
|         if ($imgs->count() < 20) { | ||||
|             return $this->getRandomImage(); | ||||
|         } | ||||
|  | ||||
|         $img_url = $imgs->eq(random_int(0, $imgs->count() - 1))->attr('src'); | ||||
|         $parsed_url = parse_url( $img_url ); | ||||
|         parse_str( $parsed_url['query'] , $url_vars ); | ||||
|         $name = $url_vars['id']; | ||||
|         unset($url_vars['w'], $url_vars['h']); | ||||
|         $parsed_url['query'] = $url_vars; | ||||
|         $img_url = $this->reverse_url($parsed_url); | ||||
|  | ||||
|         file_put_contents($this->getPoolDirectory() . '/'. $name . '.jpg', file_get_contents($img_url)); | ||||
|  | ||||
|         return 1; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param int $number | ||||
|      * | ||||
|      * @return int | ||||
|      */ | ||||
|     public function getRandomImages(int $number) | ||||
|     { | ||||
|         $count = 0; | ||||
|         for ($i = 0; $i < $number; $i++) { | ||||
|             $count += $this->getRandomImage(); | ||||
|         } | ||||
|  | ||||
|         return $count; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										72
									
								
								src/Config.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/Config.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | ||||
| <?php | ||||
|  | ||||
| namespace Shikiryu\WebGobbler; | ||||
|  | ||||
| class Config | ||||
| { | ||||
|     protected $data = null; | ||||
|  | ||||
|     protected $cache = []; | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * Config constructor. | ||||
|      * @throws \JsonException | ||||
|      * @throws \Exception | ||||
|      */ | ||||
|     public function __construct($config_file) | ||||
|     { | ||||
|         if (!is_readable($config_file)) { | ||||
|             throw new \Exception('Config file doesn\'t exist or is unreadable.'); | ||||
|         } | ||||
|  | ||||
|         $this->data = json_decode(file_get_contents($config_file), true, 512, JSON_THROW_ON_ERROR); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param      $key | ||||
|      * @param null $default | ||||
|      * | ||||
|      * @return mixed|null | ||||
|      */ | ||||
|     public function get($key, $default = null) | ||||
|     { | ||||
|         if ($this->has($key)) { | ||||
|             return $this->cache[$key]; | ||||
|         } | ||||
|  | ||||
|         return $default; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param string $key | ||||
|      * | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function has($key) | ||||
|     { | ||||
|         // Check if already cached | ||||
|         if (isset($this->cache[$key])) { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         $segments = explode('.', $key); | ||||
|         $root = $this->data; | ||||
|  | ||||
|         // nested case | ||||
|         foreach ($segments as $segment) { | ||||
|             if (array_key_exists($segment, $root)) { | ||||
|                 $root = $root[$segment]; | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         // Set cache for the given key | ||||
|         $this->cache[$key] = $root; | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
| } | ||||
							
								
								
									
										109
									
								
								src/Pool.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								src/Pool.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,109 @@ | ||||
| <?php | ||||
|  | ||||
| namespace Shikiryu\WebGobbler; | ||||
|  | ||||
| class Pool | ||||
| { | ||||
|     protected $file_list = []; | ||||
|  | ||||
|     /** | ||||
|      * @var \Shikiryu\WebGobbler\Collector[] | ||||
|      */ | ||||
|     protected $collectors = []; | ||||
|  | ||||
|     /** | ||||
|      * @var string | ||||
|      */ | ||||
|     protected $pool_directory; | ||||
|  | ||||
|     /** | ||||
|      * @var int | ||||
|      */ | ||||
|     protected $nb_images = 0; | ||||
|  | ||||
|     /** | ||||
|      * Pool constructor. | ||||
|      * | ||||
|      * @param array $config | ||||
|      */ | ||||
|     public function __construct(array $config) | ||||
|     { | ||||
|         $collectors = []; | ||||
|         if (array_key_exists('collectors', $config)) { | ||||
|             $collectors = $config['collectors']; | ||||
|         } | ||||
|  | ||||
|         $all_collectors = Collector::getAllCollectors($this); | ||||
|         if (empty($collectors)) { | ||||
|             $this->collectors = $all_collectors; | ||||
|         } else { | ||||
|             foreach ($all_collectors as $collector) { | ||||
|                 if (in_array($collector->getName(), $collectors, true)) { | ||||
|                     $this->collectors[] = $collector; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         $pool_dir = $config['directory']; | ||||
|         if (!is_dir($pool_dir) && !mkdir($pool_dir) && !is_dir($pool_dir)) { | ||||
|             throw new \RuntimeException(sprintf('Directory "%s" was not created', $pool_dir)); | ||||
|         } | ||||
|  | ||||
|         $this->pool_directory = $pool_dir; | ||||
|  | ||||
|         $this->nb_images = $config['nb_images']; // FIXME | ||||
|  | ||||
|         $this->prepareCollectors(); | ||||
|     } | ||||
|  | ||||
|     public function getPoolDirectory() | ||||
|     { | ||||
|         return $this->pool_directory; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getImage() | ||||
|     { | ||||
|         $images = $this->getFileList(); | ||||
|  | ||||
|         $index = array_rand($images); | ||||
|         $image = $images[$index]; | ||||
|  | ||||
|         unset($this->file_list[$index]); | ||||
|  | ||||
|         return $image; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @return array | ||||
|      */ | ||||
|     private function getFileList() | ||||
|     { | ||||
|         if (!empty($this->file_list)) { | ||||
|             return $this->file_list; | ||||
|         } | ||||
|  | ||||
|         $file_list = []; | ||||
|         foreach ($this->collectors as $collector) { | ||||
|             $directory = $collector->getPoolDirectory(); | ||||
|             $file_list = array_merge($file_list, glob($directory.'/*.{jpg,gif,png}', GLOB_BRACE)); | ||||
|         } | ||||
|  | ||||
|         $this->file_list = $file_list; | ||||
|  | ||||
|         return $file_list; | ||||
|     } | ||||
|  | ||||
|     private function prepareCollectors() | ||||
|     { | ||||
|         foreach ($this->collectors as $collector) { | ||||
|             $directory = $collector->getPoolDirectory(); | ||||
|             $images = glob($directory.'/*.{jpg,gif,png}', GLOB_BRACE); | ||||
|             if (count($images) < $this->nb_images) { | ||||
|                 $collector->getRandomImages($this->nb_images - count($images)); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user