DealabsBridge.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. <?php
  2. class DealabsBridge extends BridgeAbstract {
  3. const NAME = 'Dealabs search bridge';
  4. const URI = 'https://www.dealabs.com/';
  5. const DESCRIPTION = 'Return the Dealabs search result using keywords';
  6. const MAINTAINER = 'sysadminstory';
  7. const PARAMETERS = array(
  8. 'Recherche par Mot(s) clé(s)' => array (
  9. 'q' => array(
  10. 'name' => 'Mot(s) clé(s)',
  11. 'type' => 'text',
  12. 'required' => true
  13. ),
  14. 'hide_expired' => array(
  15. 'name' => 'Masquer les éléments expirés',
  16. 'type' => 'checkbox',
  17. 'required' => 'true'
  18. ),
  19. 'hide_local' => array(
  20. 'name' => 'Masquer les deals locaux',
  21. 'type' => 'checkbox',
  22. 'title' => 'Masquer les deals en magasins physiques',
  23. 'required' => 'true'
  24. ),
  25. 'priceFrom' => array(
  26. 'name' => 'Prix minimum',
  27. 'type' => 'text',
  28. 'title' => 'Prix mnimum en euros',
  29. 'required' => 'false',
  30. 'defaultValue' => ''
  31. ),
  32. 'priceTo' => array(
  33. 'name' => 'Prix maximum',
  34. 'type' => 'text',
  35. 'title' => 'Prix maximum en euros',
  36. 'required' => 'false',
  37. 'defaultValue' => ''
  38. ),
  39. ),
  40. 'Deals par groupe' => array(
  41. 'groupe' => array(
  42. 'name' => 'Groupe',
  43. 'type' => 'list',
  44. 'required' => 'true',
  45. 'title' => 'Groupe dont il faut afficher les deals',
  46. 'values' => array(
  47. 'Accessoires & gadgets' => 'accessoires-gadgets',
  48. 'Alimentation & boissons' => 'alimentation-boissons',
  49. 'Animaux' => 'animaux',
  50. 'Applis & logiciels' => 'applis-logiciels',
  51. 'Consoles & jeux vidéo' => 'consoles-jeux-video',
  52. 'Culture & divertissement' => 'culture-divertissement',
  53. 'Gratuit' => 'gratuit',
  54. 'Image, son & vidéo' => 'image-son-video',
  55. 'Informatique' => 'informatique',
  56. 'Jeux & jouets' => 'jeux-jouets',
  57. 'Maison & jardin' => 'maison-jardin',
  58. 'Mode & accessoires' => 'mode-accessoires',
  59. 'Santé & cosmétiques' => 'hygiene-sante-cosmetiques',
  60. 'Services divers' => 'services-divers',
  61. 'Sports & plein air' => 'sports-plein-air',
  62. 'Téléphonie' => 'telephonie',
  63. 'Voyages & sorties' => 'voyages-sorties-restaurants'
  64. )
  65. ),
  66. 'ordre' => array(
  67. 'name' => 'Trier par',
  68. 'type' => 'list',
  69. 'required' => 'true',
  70. 'title' => 'Ordre de tri des deals',
  71. 'values' => array(
  72. 'Du deal le plus Hot au moins Hot' => '',
  73. 'Du deal le plus récent au plus ancien' => '-nouveaux',
  74. 'Du deal le plus commentés au moins commentés' => '-commentes'
  75. )
  76. )
  77. )
  78. );
  79. const CACHE_TIMEOUT = 3600;
  80. public function collectData(){
  81. switch($this->queriedContext) {
  82. case 'Recherche par Mot(s) clé(s)':
  83. return $this->collectDataMotsCles();
  84. break;
  85. case 'Deals par groupe':
  86. return $this->collectDataGroupe();
  87. break;
  88. }
  89. }
  90. /**
  91. * Get the Deal data from the choosen groupe in the choose order
  92. */
  93. public function collectDataGroupe()
  94. {
  95. $groupe = $this->getInput('groupe');
  96. $ordre = $this->getInput('ordre');
  97. $url = self::URI
  98. . '/groupe/' . $groupe . $ordre;
  99. $this->collectDeals($url);
  100. }
  101. /**
  102. * Get the Deal data from the choosen keywords and parameters
  103. */
  104. public function collectDataMotsCles()
  105. {
  106. $q = $this->getInput('q');
  107. $hide_expired = $this->getInput('hide_expired');
  108. $hide_local = $this->getInput('hide_local');
  109. $priceFrom = $this->getInput('priceFrom');
  110. $priceTo = $this->getInput('priceFrom');
  111. /* Even if the original website uses POST with the search page, GET works too */
  112. $url = self::URI
  113. . '/search/advanced?q='
  114. . urlencode($q)
  115. . '&hide_expired='. $hide_expired
  116. . '&hide_local='. $hide_local
  117. . '&priceFrom='. $priceFrom
  118. . '&priceTo='. $priceTo
  119. /* Some default parameters
  120. * search_fields : Search in Titres & Descriptions & Codes
  121. * sort_by : Sort the search by new deals
  122. * time_frame : Search will not be on a limited timeframe
  123. */
  124. . '&search_fields[]=1&search_fields[]=2&search_fields[]=3&sort_by=new&time_frame=0';
  125. $this->collectDeals($url);
  126. }
  127. /**
  128. * Get the Deal data using the given URL
  129. */
  130. public function collectDeals($url){
  131. $html = getSimpleHTMLDOM($url)
  132. or returnServerError('Could not request Dealabs.');
  133. $list = $html->find('article');
  134. // Deal Image Link CSS Selector
  135. $selectorImageLink = implode(
  136. ' ', /* Notice this is a space! */
  137. array(
  138. 'cept-thread-image-link',
  139. 'imgFrame',
  140. 'imgFrame--noBorder',
  141. 'thread-listImgCell',
  142. )
  143. );
  144. // Deal Link CSS Selector
  145. $selectorLink = implode(
  146. ' ', /* Notice this is a space! */
  147. array(
  148. 'cept-tt',
  149. 'thread-link',
  150. 'linkPlain',
  151. )
  152. );
  153. // Deal Hotness CSS Selector
  154. $selectorHot = implode(
  155. ' ', /* Notice this is a space! */
  156. array(
  157. 'flex',
  158. 'flex--align-c',
  159. 'flex--justify-space-between',
  160. 'space--b-2',
  161. )
  162. );
  163. // Deal Description CSS Selector
  164. $selectorDescription = implode(
  165. ' ', /* Notice this is a space! */
  166. array(
  167. 'cept-description-container',
  168. 'overflow--wrap-break',
  169. 'size--all-s',
  170. 'size--fromW3-m'
  171. )
  172. );
  173. // Deal Date CSS Selector
  174. $selectorDate = implode(
  175. ' ', /* Notice this is a space! */
  176. array(
  177. 'size--all-s',
  178. 'flex',
  179. 'flex--justify-e',
  180. 'flex--grow-1',
  181. )
  182. );
  183. // If there is no results, we don't parse the content because it display some random deals
  184. $noresult = $html->find('h3[class=size--all-l size--fromW2-xl size--fromW3-xxl]', 0);
  185. if($noresult != null && $noresult->plaintext == 'Il n&#039;y a rien à afficher pour le moment :(') {
  186. $this->items = array();
  187. } else {
  188. foreach($list as $deal) {
  189. $item = array();
  190. $item['uri'] = $deal->find('div[class=threadGrid-title]', 0)->find('a', 0)->href;
  191. $item['title'] = $deal->find('a[class*='. $selectorLink .']', 0
  192. )->plaintext;
  193. $item['author'] = $deal->find('span.thread-username', 0)->plaintext;
  194. $item['content'] = '<table><tr><td><a href="'
  195. . $deal->find(
  196. 'a[class*='. $selectorImageLink .']', 0)->href
  197. . '"><img src="'
  198. . $this->getImage($deal)
  199. . '"/></td><td><h2><a href="'
  200. . $deal->find('a[class*='. $selectorLink .']', 0)->href
  201. . '">'
  202. . $deal->find('a[class*='. $selectorLink .']', 0)->innertext
  203. . '</a></h2>'
  204. . $this->getPrix($deal)
  205. . $this->getReduction($deal)
  206. . $this->getExpedition($deal)
  207. . $this->getLivraison($deal)
  208. . $this->getOrigine($deal)
  209. . $deal->find('div[class*='. $selectorDescription .']', 0)->innertext
  210. . '</td><td>'
  211. . $deal->find('div[class='. $selectorHot .']', 0)->children(0)->outertext
  212. . '</td></table>';
  213. $dealDateDiv = $deal->find('div[class*='. $selectorDate .']', 0)
  214. ->find('span[class=hide--toW3]');
  215. $itemDate = end($dealDateDiv)->plaintext;
  216. if(substr( $itemDate, 0, 6 ) === 'il y a') {
  217. $item['timestamp'] = $this->relativeDateToTimestamp($itemDate);
  218. } else {
  219. $item['timestamp'] = $this->parseDate($itemDate);
  220. }
  221. $this->items[] = $item;
  222. }
  223. }
  224. }
  225. /**
  226. * Get the Price from a Deal if it exists
  227. * @return string String of the deal price
  228. */
  229. private function getPrix($deal)
  230. {
  231. if($deal->find(
  232. 'span[class*=thread-price]', 0) != null) {
  233. return '<div>Prix : '
  234. . $deal->find(
  235. 'span[class*=thread-price]', 0
  236. )->plaintext
  237. . '</div>';
  238. } else {
  239. return '';
  240. }
  241. }
  242. /**
  243. * Get the Shipping costs from a Deal if it exists
  244. * @return string String of the deal shipping Cost
  245. */
  246. private function getLivraison($deal)
  247. {
  248. if($deal->find('span[class*=cept-shipping-price]', 0) != null) {
  249. if($deal->find('span[class*=cept-shipping-price]', 0)->children(0) != null) {
  250. return '<div>Livraison : '
  251. . $deal->find('span[class*=cept-shipping-price]', 0)->children(0)->innertext
  252. . '</div>';
  253. } else {
  254. return '<div>Livraison : '
  255. . $deal->find('span[class*=cept-shipping-price]', 0)->innertext
  256. . '</div>';
  257. }
  258. } else {
  259. return '';
  260. }
  261. }
  262. /**
  263. * Get the source of a Deal if it exists
  264. * @return string String of the deal source
  265. */
  266. private function getOrigine($deal)
  267. {
  268. if($deal->find('a[class=text--color-greyShade]', 0) != null) {
  269. return '<div>Origine : '
  270. . $deal->find('a[class=text--color-greyShade]', 0)->outertext
  271. . '</div>';
  272. } else {
  273. return '';
  274. }
  275. }
  276. /**
  277. * Get the original Price and discout from a Deal if it exists
  278. * @return string String of the deal original price and discount
  279. */
  280. private function getReduction($deal)
  281. {
  282. if($deal->find('span[class*=mute--text text--lineThrough]', 0) != null) {
  283. $discountHtml = $deal->find('span[class=space--ml-1 size--all-l size--fromW3-xl]', 0);
  284. if($discountHtml != null) {
  285. $discount = $discountHtml->plaintext;
  286. } else {
  287. $discount = '';
  288. }
  289. return '<div>Réduction : <span style="text-decoration: line-through;">'
  290. . $deal->find(
  291. 'span[class*=mute--text text--lineThrough]', 0
  292. )->plaintext
  293. . '</span>&nbsp;'
  294. . $discount
  295. . '</div>';
  296. } else {
  297. return '';
  298. }
  299. }
  300. /**
  301. * Get the Picture URL from a Deal if it exists
  302. * @return string String of the deal Picture URL
  303. */
  304. private function getImage($deal)
  305. {
  306. $selectorLazy = implode(
  307. ' ', /* Notice this is a space! */
  308. array(
  309. 'thread-image',
  310. 'width--all-auto',
  311. 'height--all-auto',
  312. 'imgFrame-img',
  313. 'cept-thread-img',
  314. 'img--dummy',
  315. 'js-lazy-img'
  316. )
  317. );
  318. $selectorPlain = implode(
  319. ' ', /* Notice this is a space! */
  320. array(
  321. 'thread-image',
  322. 'width--all-auto',
  323. 'height--all-auto',
  324. 'imgFrame-img',
  325. 'cept-thread-img'
  326. )
  327. );
  328. if($deal->find('img[class='. $selectorLazy .']', 0) != null) {
  329. return json_decode(
  330. html_entity_decode(
  331. $deal->find('img[class='. $selectorLazy .']', 0)
  332. ->getAttribute('data-lazy-img')))->{'src'};
  333. } else {
  334. return $deal->find('img[class='. $selectorPlain .']', 0 )->src;
  335. }
  336. }
  337. /**
  338. * Get the originating country from a Deal if it existsa
  339. * @return string String of the deal originating country
  340. */
  341. private function getExpedition($deal)
  342. {
  343. $selector = implode(
  344. ' ', /* Notice this is a space! */
  345. array(
  346. 'meta-ribbon',
  347. 'overflow--wrap-off',
  348. 'space--l-3',
  349. 'text--color-greyShade'
  350. )
  351. );
  352. if($deal->find('span[class='. $selector .']', 0) != null) {
  353. return '<div>'
  354. . $deal->find('span[class='. $selector .']', 0)->children(2)->plaintext
  355. . '</div>';
  356. } else {
  357. return '';
  358. }
  359. }
  360. /**
  361. * Transforms a French date into a timestam
  362. * @return int timestamp of the input date
  363. */
  364. private function parseDate($string)
  365. {
  366. $month_fr = array(
  367. 'janvier',
  368. 'février',
  369. 'mars',
  370. 'avril',
  371. 'mai',
  372. 'juin',
  373. 'juillet',
  374. 'août',
  375. 'septembre',
  376. 'octobre',
  377. 'novembre',
  378. 'décembre'
  379. );
  380. $month_en = array(
  381. 'January',
  382. 'February',
  383. 'March',
  384. 'April',
  385. 'May',
  386. 'June',
  387. 'July',
  388. 'August',
  389. 'September',
  390. 'October',
  391. 'November',
  392. 'December'
  393. );
  394. $string = str_replace('Actualisé ', '', $string);
  395. $date_str = trim(str_replace($month_fr, $month_en, $string));
  396. if(!preg_match('/[0-9]{4}/', $string)) {
  397. $date_str .= ' ' . date('Y');
  398. }
  399. $date_str .= ' 00:00';
  400. $date = DateTime::createFromFormat('j F Y H:i', $date_str);
  401. return $date->getTimestamp();
  402. }
  403. /**
  404. * Transforms a relate French date into a timestam
  405. * @return int timestamp of the input date
  406. */
  407. private function relativeDateToTimestamp($str) {
  408. $date = new DateTime();
  409. $search = array(
  410. 'il y a ',
  411. 'min',
  412. 'h',
  413. 'jour',
  414. 'jours',
  415. 'mois',
  416. 'ans',
  417. 'et '
  418. );
  419. $replace = array(
  420. '-',
  421. 'minute',
  422. 'hour',
  423. 'day',
  424. 'month',
  425. 'year',
  426. ''
  427. );
  428. $date->modify(str_replace($search, $replace, $str));
  429. return $date->getTimestamp();
  430. }
  431. public function getName(){
  432. switch($this->queriedContext) {
  433. case 'Recherche par Mot(s) clé(s)':
  434. return self::NAME . ' - Recherche : '. $this->getInput('q');
  435. break;
  436. case 'Deals par groupe':
  437. $values = self::PARAMETERS['Deals par groupe']['groupe']['values'];
  438. $groupe = array_search($this->getInput('groupe'), $values);
  439. return self::NAME . ' - Groupe : '. $groupe;
  440. break;
  441. default: // Return default value
  442. return self::NAME;
  443. }
  444. }
  445. }