API client for Kitsu.io, with optional Anime collection, and optional Anilist syncing.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AnimeClient.php 6.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. <?php declare(strict_types=1);
  2. /**
  3. * Hummingbird Anime List Client
  4. *
  5. * An API client for Kitsu to manage anime and manga watch lists
  6. *
  7. * PHP version 7.1
  8. *
  9. * @package HummingbirdAnimeClient
  10. * @author Timothy J. Warren <tim@timshomepage.net>
  11. * @copyright 2015 - 2018 Timothy J. Warren
  12. * @license http://www.opensource.org/licenses/mit-license.html MIT License
  13. * @version 4.1
  14. * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
  15. */
  16. namespace Aviat\AnimeClient;
  17. use Aviat\Ion\ConfigInterface;
  18. use Yosymfony\Toml\{Toml, TomlBuilder};
  19. // ----------------------------------------------------------------------------
  20. //! TOML Functions
  21. // ----------------------------------------------------------------------------
  22. /**
  23. * Load configuration options from .toml files
  24. *
  25. * @param string $path - Path to load config
  26. * @return array
  27. */
  28. function loadToml(string $path): array
  29. {
  30. $output = [];
  31. $files = glob("{$path}/*.toml");
  32. foreach ($files as $file)
  33. {
  34. $key = str_replace('.toml', '', basename($file));
  35. if ($key === 'admin-override')
  36. {
  37. continue;
  38. }
  39. $config = Toml::parseFile($file);
  40. if ($key === 'config')
  41. {
  42. foreach($config as $name => $value)
  43. {
  44. $output[$name] = $value;
  45. }
  46. continue;
  47. }
  48. $output[$key] = $config;
  49. }
  50. return $output;
  51. }
  52. /**
  53. * Load config from one specific TOML file
  54. *
  55. * @param string $filename
  56. * @return array
  57. */
  58. function loadTomlFile(string $filename): array
  59. {
  60. return Toml::parseFile($filename);
  61. }
  62. /**
  63. * Recursively create a toml file from a data array
  64. *
  65. * @param TomlBuilder $builder
  66. * @param iterable $data
  67. * @param null $parentKey
  68. */
  69. function _iterateToml(TomlBuilder $builder, iterable $data, $parentKey = NULL): void
  70. {
  71. foreach ($data as $key => $value)
  72. {
  73. if ($value === NULL)
  74. {
  75. continue;
  76. }
  77. if (is_scalar($value) || isSequentialArray($value))
  78. {
  79. // $builder->addTable('');
  80. $builder->addValue($key, $value);
  81. continue;
  82. }
  83. $newKey = ($parentKey !== NULL)
  84. ? "{$parentKey}.{$key}"
  85. : $key;
  86. if ( ! isSequentialArray($value))
  87. {
  88. $builder->addTable($newKey);
  89. }
  90. _iterateToml($builder, $value, $newKey);
  91. }
  92. }
  93. /**
  94. * Serialize config data into a Toml file
  95. *
  96. * @param mixed $data
  97. * @return string
  98. */
  99. function arrayToToml(iterable $data): string
  100. {
  101. $builder = new TomlBuilder();
  102. _iterateToml($builder, $data);
  103. return $builder->getTomlString();
  104. }
  105. /**
  106. * Serialize toml back to an array
  107. *
  108. * @param string $toml
  109. * @return array
  110. */
  111. function tomlToArray(string $toml): array
  112. {
  113. return Toml::parse($toml);
  114. }
  115. // ----------------------------------------------------------------------------
  116. //! Misc Functions
  117. // ----------------------------------------------------------------------------
  118. /**
  119. * Is the array sequential, not associative?
  120. *
  121. * @param mixed $array
  122. * @return bool
  123. */
  124. function isSequentialArray($array): bool
  125. {
  126. if ( ! is_array($array))
  127. {
  128. return FALSE;
  129. }
  130. $i = 0;
  131. foreach ($array as $k => $v)
  132. {
  133. if ($k !== $i++)
  134. {
  135. return FALSE;
  136. }
  137. }
  138. return TRUE;
  139. }
  140. /**
  141. * Check that folder permissions are correct for proper operation
  142. *
  143. * @param ConfigInterface $config
  144. * @return array
  145. */
  146. function checkFolderPermissions(ConfigInterface $config): array
  147. {
  148. $errors = [];
  149. $publicDir = $config->get('asset_dir');
  150. $pathMap = [
  151. 'app/config' => realpath(__DIR__ . '/../app/config'),
  152. 'app/logs' => realpath(__DIR__ . '/../app/logs'),
  153. 'public/images/avatars' => "{$publicDir}/images/avatars",
  154. 'public/images/anime' => "{$publicDir}/images/anime",
  155. 'public/images/characters' => "{$publicDir}/images/characters",
  156. 'public/images/manga' => "{$publicDir}/images/manga",
  157. 'public/images/people' => "{$publicDir}/images/people",
  158. ];
  159. foreach ($pathMap as $pretty => $actual)
  160. {
  161. // Make sure the folder exists first
  162. if ( ! is_dir($actual))
  163. {
  164. $errors['missing'][] = $pretty;
  165. continue;
  166. }
  167. $writable = is_writable($actual) && is_executable($actual);
  168. if ( ! $writable)
  169. {
  170. $errors['writable'][] = $pretty;
  171. }
  172. }
  173. return $errors;
  174. }
  175. /**
  176. * Generate the path for the cached image from the original image
  177. *
  178. * @param string $kitsuUrl
  179. * @param bool $webp
  180. * @return string
  181. */
  182. function getLocalImg ($kitsuUrl, $webp = TRUE): string
  183. {
  184. if ( ! is_string($kitsuUrl))
  185. {
  186. return 'images/placeholder.webp';
  187. }
  188. $parts = parse_url($kitsuUrl);
  189. if ($parts === FALSE)
  190. {
  191. return 'images/placeholder.webp';
  192. }
  193. $file = basename($parts['path']);
  194. $fileParts = explode('.', $file);
  195. $ext = array_pop($fileParts);
  196. $ext = $webp ? 'webp' : $ext;
  197. $segments = explode('/', trim($parts['path'], '/'));
  198. $type = $segments[0] === 'users' ? $segments[1] : $segments[0];
  199. $id = $segments[count($segments) - 2];
  200. return implode('/', ['images', $type, "{$id}.{$ext}"]);
  201. }
  202. /**
  203. * Create a transparent placeholder image
  204. *
  205. * @param string $path
  206. * @param int $width
  207. * @param int $height
  208. * @param string $text
  209. */
  210. function createPlaceholderImage ($path, $width, $height, $text = 'Image Unavailable')
  211. {
  212. $width = $width ?? 200;
  213. $height = $height ?? 200;
  214. $img = imagecreatetruecolor($width, $height);
  215. imagealphablending($img, TRUE);
  216. $path = rtrim($path, '/');
  217. // Background is the first color by default
  218. $fillColor = imagecolorallocatealpha($img, 255, 255, 255, 127);
  219. imagefill($img, 0, 0, $fillColor);
  220. $textColor = imagecolorallocate($img, 64, 64, 64);
  221. imagealphablending($img, TRUE);
  222. // Generate placeholder text
  223. $fontSize = 10;
  224. $fontWidth = imagefontwidth($fontSize);
  225. $fontHeight = imagefontheight($fontSize);
  226. $length = strlen($text);
  227. $textWidth = $length * $fontWidth;
  228. $fxPos = (int) ceil((imagesx($img) - $textWidth) / 2);
  229. $fyPos = (int) ceil((imagesy($img) - $fontHeight) / 2);
  230. // Add the image text
  231. imagestring($img, $fontSize, $fxPos, $fyPos, $text, $textColor);
  232. // Save the images
  233. imagesavealpha($img, TRUE);
  234. imagepng($img, $path . '/placeholder.png', 9);
  235. imagedestroy($img);
  236. $pngImage = imagecreatefrompng($path . '/placeholder.png');
  237. imagealphablending($pngImage, TRUE);
  238. imagesavealpha($pngImage, TRUE);
  239. imagewebp($pngImage, $path . '/placeholder.webp');
  240. imagedestroy($pngImage);
  241. }