plugin.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. <?php
  2. // PLUGIN INFORMATION
  3. $GLOBALS['plugins']['ShuckStop'] = [ // Plugin Name
  4. 'name' => 'ShuckStop', // Plugin Name
  5. 'author' => 'CauseFX', // Who wrote the plugin
  6. 'category' => 'Utilities', // One to Two Word Description
  7. 'link' => '', // Link to plugin info
  8. 'license' => 'personal,business', // License Type use , for multiple
  9. 'idPrefix' => 'SHUCKSTOP', // html element id prefix
  10. 'configPrefix' => 'SHUCKSTOP', // config file prefix for array items without the hyphen
  11. 'version' => '1.0.0', // SemVer of plugin
  12. 'image' => 'api/plugins/shuck-stop/logo.png', // 1:1 non transparent image for plugin
  13. 'settings' => true, // does plugin need a settings modal?
  14. 'bind' => true, // use default bind to make settings page - true or false
  15. 'api' => 'api/v2/plugins/shuck-stop/settings', // api route for settings page
  16. 'homepage' => false // Is plugin for use on homepage? true or false
  17. ];
  18. class ShuckStop extends Organizr
  19. {
  20. public function _shuckStopPluginGetSettings()
  21. {
  22. return [
  23. 'Cron' => [
  24. $this->settingsOption('cron-file'),
  25. $this->settingsOption('blank'),
  26. $this->settingsOption('enable', 'SHUCKSTOP-cron-run-enabled'),
  27. $this->settingsOption('cron', 'SHUCKSTOP-cron-run-schedule')
  28. ],
  29. 'Email' => [
  30. $this->settingsOption('multiple', 'SHUCKSTOP-emails', ['label' => 'Emails']),
  31. ],
  32. 'Model' => [
  33. $this->settingsOption('switch', 'SHUCKSTOP-easystore', ['label' => 'Monitor EasyStore']),
  34. $this->settingsOption('switch', 'SHUCKSTOP-my-book', ['label' => 'Monitor My Book']),
  35. $this->settingsOption('switch', 'SHUCKSTOP-elements', ['label' => 'Monitor Elements']),
  36. ],
  37. 'Capacity' => [
  38. $this->settingsOption('switch', 'SHUCKSTOP-8', ['label' => 'Monitor 8TB']),
  39. $this->settingsOption('switch', 'SHUCKSTOP-10', ['label' => 'Monitor 10TB']),
  40. $this->settingsOption('switch', 'SHUCKSTOP-12', ['label' => 'Monitor 12TB']),
  41. $this->settingsOption('switch', 'SHUCKSTOP-14', ['label' => 'Monitor 14TB']),
  42. $this->settingsOption('switch', 'SHUCKSTOP-16', ['label' => 'Monitor 16TB']),
  43. $this->settingsOption('switch', 'SHUCKSTOP-18', ['label' => 'Monitor 18TB']),
  44. $this->settingsOption('switch', 'SHUCKSTOP-20', ['label' => 'Monitor 20TB']),
  45. ]
  46. ];
  47. }
  48. public function _shuckStopPluginRun()
  49. {
  50. if ($this->config['SHUCKSTOP-enabled'] && !empty($this->config['SHUCKSTOP-emails']) && $this->qualifyRequest(1)) {
  51. if (
  52. ($this->config['SHUCKSTOP-easystore'] ||
  53. $this->config['SHUCKSTOP-my-book'] ||
  54. $this->config['SHUCKSTOP-elements']
  55. ) &&
  56. ($this->config['SHUCKSTOP-8'] ||
  57. $this->config['SHUCKSTOP-10'] ||
  58. $this->config['SHUCKSTOP-12'] ||
  59. $this->config['SHUCKSTOP-14'] ||
  60. $this->config['SHUCKSTOP-16'] ||
  61. $this->config['SHUCKSTOP-18'] ||
  62. $this->config['SHUCKSTOP-20']
  63. )
  64. ) {
  65. $file = $this->root . DIRECTORY_SEPARATOR . 'api' . DIRECTORY_SEPARATOR . 'plugins' . DIRECTORY_SEPARATOR . 'shuck-stop' . DIRECTORY_SEPARATOR . 'drives.json';
  66. $hasFile = file_exists($file);
  67. $json = null;
  68. if ($hasFile && filesize($file) > 0) {
  69. $jsonFile = file_get_contents($file);
  70. $json = json_decode($jsonFile, true);
  71. }
  72. $url = 'https://shucks.top/';
  73. $options = ($this->localURL($url)) ? ['verify' => false] : [];
  74. $response = Requests::get($url, [], $options);
  75. if ($response->success) {
  76. $drives = [
  77. 'run_date' => $this->currentTime,
  78. 'has_file' => $hasFile,
  79. 'email_setup' => $this->config['PHPMAILER-enabled'],
  80. 'last_email_drives_lowest' => ($json) ? $json['last_email_drives_lowest'] : [],
  81. 'last_email_drives_decent' => ($json) ? $json['last_email_drives_decent'] : [],
  82. 'lowest_priced_drives' => [],
  83. 'recent_decent_drives' => [],
  84. ];
  85. $dom = new PHPHtmlParser\Dom;
  86. try {
  87. $dom->loadStr($response->body);
  88. $contents = $dom->find('tbody tr');
  89. foreach ($contents as $content) {
  90. //$html = $content->innerHtml;
  91. $capacity = $content->getAttribute('data-capacity');
  92. $model = str_replace(' ', '-', $content->find('td')[1]->text);
  93. $lastDecent = $content->find('td')[8]->text;
  94. $lowestDollars = $content->find('td')[7]->getAttribute('data-dollars');
  95. $lowestPerTB = $content->find('td')[7]->getAttribute('data-per-tb');
  96. $lowestNow = $content->find('td')[7]->find('p')[1]->text;
  97. $drives['drives'][$capacity][strtolower($model)]['capacity'] = $capacity;
  98. $drives['drives'][$capacity][strtolower($model)]['model'] = strtolower($model);
  99. $drives['drives'][$capacity][strtolower($model)]['last_decent'] = $lastDecent;
  100. $drives['drives'][$capacity][strtolower($model)]['lowest_dollars'] = $lowestDollars;
  101. $drives['drives'][$capacity][strtolower($model)]['lowest_decent_dollars'] = 100000;
  102. $drives['drives'][$capacity][strtolower($model)]['lowest_per_tb'] = $lowestPerTB;
  103. $drives['drives'][$capacity][strtolower($model)]['lowest_now'] = $lowestNow == 'now';
  104. $drives['drives'][$capacity][strtolower($model)]['decent_now'] = $lastDecent == 'now';
  105. $checkItems = [
  106. 'amazon' => $this->_checkShuckClassNA('amazon', $content->find('td')[2]->getAttribute('class')),
  107. 'bestbuy' => $this->_checkShuckClassNA('bestbuy', $content->find('td')[3]->getAttribute('class')),
  108. 'bhphoto' => $this->_checkShuckClassNA('bhphoto', $content->find('td')[4]->getAttribute('class')),
  109. 'ebay' => $this->_checkShuckClassNA('ebay', $content->find('td')[5]->getAttribute('class')),
  110. 'newegg' => $this->_checkShuckClassNA('newegg', $content->find('td')[6]->getAttribute('class'))
  111. ];
  112. $i = 2;
  113. foreach ($checkItems as $store => $class) {
  114. if ($class) {
  115. $driveInfo = $class;
  116. } else {
  117. $driveInfo = $this->_checkShuckStore($store, [
  118. 'data-per-tb' => $content->find('td')[$i]->getAttribute('data-per-tb'),
  119. 'data-dollars' => $content->find('td')[$i]->getAttribute('data-dollars'),
  120. 'title' => $content->find('td')[$i]->getAttribute('title'),
  121. 'link' => $content->find('td')[$i]->find('a')->getAttribute('href'),
  122. 'lowest_dollars' => $lowestDollars,
  123. 'lowest_now' => $lowestNow == 'now'
  124. ]);
  125. }
  126. $i++;
  127. $drives['drives'][$capacity][strtolower($model)]['lowest_decent_dollars'] = ($driveInfo['data-dollars'] <= $drives['drives'][$capacity][strtolower($model)]['lowest_decent_dollars'] && $driveInfo['data-dollars'] !== 0 && $driveInfo['data-dollars'] !== null) ? $driveInfo['data-dollars'] : $drives['drives'][$capacity][strtolower($model)]['lowest_decent_dollars'];
  128. $drives['drives'][$capacity][strtolower($model)]['stores'][$store] = $driveInfo;
  129. }
  130. if ($drives['drives'][$capacity][strtolower($model)]['lowest_now']) {
  131. $drives['lowest_priced_drives'][$capacity][strtolower($model)] = $lowestDollars;
  132. }
  133. if ($drives['drives'][$capacity][strtolower($model)]['decent_now']) {
  134. $drives['recent_decent_drives'][$capacity][strtolower($model)] = $drives['drives'][$capacity][strtolower($model)]['lowest_decent_dollars'];
  135. }
  136. }
  137. // Run the checks...
  138. $capacities = [8, 10, 12, 14, 16, 18, 20];
  139. $models = ['easystore', 'elements', 'my-book'];
  140. $emailBody = '';
  141. foreach ($capacities as $capacity) {
  142. if ($this->config['SHUCKSTOP-' . $capacity]) {
  143. foreach ($models as $model) {
  144. if ($this->config['SHUCKSTOP-' . $model]) {
  145. if (isset($drives['lowest_priced_drives'][$capacity][$model]) &&
  146. (!$json ||
  147. (isset($json['lowest_priced_drives'][$capacity][$model]) &&
  148. $drives['lowest_priced_drives'][$capacity][$model] !== $json['last_email_drives_lowest'][$capacity][$model])
  149. )
  150. ) {
  151. $emailBody .= '<br/>The ' . $capacity . 'TB drive is at the lowest price of $' . $drives['lowest_priced_drives'][$capacity][$model];
  152. foreach ($drives['drives'][$capacity][$model]['stores'] as $store => $storeInfo) {
  153. if ($storeInfo['data-dollars'] == $drives['lowest_priced_drives'][$capacity][$model]) {
  154. if ($storeInfo['link'] !== '') {
  155. $emailBody .= '<br/><a href="' . $storeInfo['link'] . '">' . $store . '</a>';
  156. }
  157. }
  158. }
  159. $drives['last_email_drives_lowest'][$capacity][$model] = $drives['lowest_priced_drives'][$capacity][$model];
  160. }
  161. if (isset($drives['recent_decent_drives'][$capacity][$model]) &&
  162. (!$json ||
  163. (isset($json['recent_decent_drives'][$capacity][$model]) &&
  164. $drives['recent_decent_drives'][$capacity][$model] !== $json['last_email_drives_decent'][$capacity][$model])
  165. )
  166. ) {
  167. $emailBody .= '<br/>The ' . $capacity . 'TB drive has dropped to the price of $' . $drives['recent_decent_drives'][$capacity][$model];
  168. foreach ($drives['drives'][$capacity][$model]['stores'] as $store => $storeInfo) {
  169. if ($storeInfo['data-dollars'] == $drives['recent_decent_drives'][$capacity][$model]) {
  170. if ($storeInfo['link'] !== '') {
  171. $emailBody .= '<br/><a href="' . $storeInfo['link'] . '">' . $store . '</a>';
  172. }
  173. }
  174. }
  175. $drives['last_email_drives_decent'][$capacity][$model] = $drives['recent_decent_drives'][$capacity][$model];
  176. }
  177. }
  178. }
  179. }
  180. }
  181. // Send email if setup
  182. if ($this->config['PHPMAILER-enabled'] && $emailBody !== '') {
  183. $PhpMailer = new PhpMailer();
  184. $emailTemplate = [
  185. 'type' => 'shuckstop',
  186. 'body' => $emailBody,
  187. 'subject' => 'New Shuck Drive Alert!',
  188. 'user' => null,
  189. 'password' => null,
  190. 'inviteCode' => null,
  191. ];
  192. $emailTemplate = $PhpMailer->_phpMailerPluginEmailTemplate($emailTemplate);
  193. $sendEmail = [
  194. 'bcc' => $this->config['SHUCKSTOP-emails'],
  195. 'subject' => $emailTemplate['subject'],
  196. 'body' => $PhpMailer->_phpMailerPluginBuildEmail($emailTemplate),
  197. ];
  198. $PhpMailer->_phpMailerPluginSendEmail($sendEmail);
  199. }
  200. // Write file
  201. file_put_contents($file, safe_json_encode($drives, JSON_HEX_QUOT | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
  202. $this->setResponse(200, null, $drives);
  203. return $drives;
  204. } catch (\PHPHtmlParser\Exceptions\ChildNotFoundException|\PHPHtmlParser\Exceptions\CircularException|\PHPHtmlParser\Exceptions\LogicalException|\PHPHtmlParser\Exceptions\StrictException|\PHPHtmlParser\Exceptions\ContentLengthException|\PHPHtmlParser\Exceptions\NotLoadedException $e) {
  205. $this->setResponse(500, 'Error connecting to ShuckStop');
  206. return false;
  207. }
  208. }
  209. $this->setResponse(500, 'Error connecting to ShuckStop');
  210. } else {
  211. $this->setResponse(409, 'No Drives are monitored');
  212. }
  213. } else {
  214. $this->setResponse(401, 'User does not have access or user email not setup');
  215. }
  216. return false;
  217. }
  218. public function _checkShuckStore($store, $info)
  219. {
  220. return [
  221. 'store' => $store,
  222. 'data-per-tb' => $info['data-per-tb'],
  223. 'data-dollars' => $info['data-dollars'],
  224. 'title' => $info['title'],
  225. 'link' => $info['link'],
  226. 'lowest_now' => $info['lowest_now'] && $info['data-dollars'] == $info['lowest_dollars']
  227. ];
  228. }
  229. public function _checkShuckClassNA($store, $class)
  230. {
  231. if ($class == 'n-a') {
  232. return [
  233. 'store' => $store,
  234. 'data-per-tb' => 0,
  235. 'data-dollars' => 0,
  236. 'title' => '',
  237. 'link' => '',
  238. 'lowest_now' => false
  239. ];
  240. } else {
  241. return false;
  242. }
  243. }
  244. }