DealabsBridge.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  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. 'box--all-i',
  142. 'thread-listImgCell',
  143. )
  144. );
  145. // Deal Link CSS Selector
  146. $selectorLink = implode(
  147. ' ', /* Notice this is a space! */
  148. array(
  149. 'cept-tt',
  150. 'thread-link',
  151. 'linkPlain',
  152. )
  153. );
  154. // Deal Hotness CSS Selector
  155. $selectorHot = implode(
  156. ' ', /* Notice this is a space! */
  157. array(
  158. 'flex',
  159. 'flex--align-c',
  160. 'flex--justify-space-between',
  161. 'space--b-2',
  162. )
  163. );
  164. // Deal Description CSS Selector
  165. $selectorDescription = implode(
  166. ' ', /* Notice this is a space! */
  167. array(
  168. 'cept-description-container',
  169. 'overflow--wrap-break',
  170. 'size--all-s',
  171. 'size--fromW3-m',
  172. )
  173. );
  174. // Deal Date CSS Selector
  175. $selectorDate = implode(
  176. ' ', /* Notice this is a space! */
  177. array(
  178. 'size--all-s',
  179. 'flex',
  180. 'flex--wrap',
  181. 'flex--justify-e',
  182. 'flex--grow-1',
  183. )
  184. );
  185. // If there is no results, we don't parse the content because it display some random deals
  186. $noresult = $html->find('h3[class=size--all-l size--fromW2-xl size--fromW3-xxl]', 0);
  187. if($noresult != null && $noresult->plaintext == 'Il n&#039;y a rien à afficher pour le moment :(') {
  188. $this->items = array();
  189. } else {
  190. foreach($list as $deal) {
  191. $item = array();
  192. $item['uri'] = $deal->find('div[class=threadGrid-title]', 0)->find('a', 0)->href;
  193. $item['title'] = $deal->find('a[class*='. $selectorLink .']', 0
  194. )->plaintext;
  195. $item['author'] = $deal->find('span.thread-username', 0)->plaintext;
  196. $item['content'] = '<table><tr><td><a href="'
  197. . $deal->find(
  198. 'a[class*='. $selectorImageLink .']', 0)->href
  199. . '"><img src="'
  200. . $this->getImage($deal)
  201. . '"/></td><td><h2><a href="'
  202. . $deal->find('a[class*='. $selectorLink .']', 0)->href
  203. . '">'
  204. . $deal->find('a[class*='. $selectorLink .']', 0)->innertext
  205. . '</a></h2>'
  206. . $this->getPrix($deal)
  207. . $this->getReduction($deal)
  208. . $this->getExpedition($deal)
  209. . $this->getLivraison($deal)
  210. . $this->getOrigine($deal)
  211. . $deal->find('div[class='. $selectorDescription .']', 0)->innertext
  212. . '</td><td>'
  213. . $deal->find('div[class='. $selectorHot .']', 0)->children(0)->outertext
  214. . '</td></table>';
  215. $dealDateDiv = $deal->find('div[class='. $selectorDate .']', 0)
  216. ->find('span[class=hide--toW3]');
  217. $itemDate = end($dealDateDiv)->plaintext;
  218. if(substr( $itemDate, 0, 6 ) === 'il y a') {
  219. $item['timestamp'] = $this->relativeDateToTimestamp($itemDate);
  220. } else {
  221. $item['timestamp'] = $this->parseDate($itemDate);
  222. }
  223. $this->items[] = $item;
  224. }
  225. }
  226. }
  227. /**
  228. * Get the Price from a Deal if it exists
  229. * @return string String of the deal price
  230. */
  231. private function getPrix($deal)
  232. {
  233. if($deal->find(
  234. 'span[class*=thread-price]', 0) != null) {
  235. return '<div>Prix : '
  236. . $deal->find(
  237. 'span[class*=thread-price]', 0
  238. )->plaintext
  239. . '</div>';
  240. } else {
  241. return '';
  242. }
  243. }
  244. /**
  245. * Get the Shipping costs from a Deal if it exists
  246. * @return string String of the deal shipping Cost
  247. */
  248. private function getLivraison($deal)
  249. {
  250. if($deal->find('span[class*=cept-shipping-price]', 0) != null) {
  251. if($deal->find('span[class*=cept-shipping-price]', 0)->children(0) != null) {
  252. return '<div>Livraison : '
  253. . $deal->find('span[class*=cept-shipping-price]', 0)->children(0)->innertext
  254. . '</div>';
  255. } else {
  256. return '<div>Livraison : '
  257. . $deal->find('span[class*=cept-shipping-price]', 0)->innertext
  258. . '</div>';
  259. }
  260. } else {
  261. return '';
  262. }
  263. }
  264. /**
  265. * Get the source of a Deal if it exists
  266. * @return string String of the deal source
  267. */
  268. private function getOrigine($deal)
  269. {
  270. if($deal->find('a[class=text--color-greyShade]', 0) != null) {
  271. return '<div>Origine : '
  272. . $deal->find('a[class=text--color-greyShade]', 0)->outertext
  273. . '</div>';
  274. } else {
  275. return '';
  276. }
  277. }
  278. /**
  279. * Get the original Price and discout from a Deal if it exists
  280. * @return string String of the deal original price and discount
  281. */
  282. private function getReduction($deal)
  283. {
  284. if($deal->find('span[class*=mute--text text--lineThrough]', 0) != null) {
  285. return '<div>Réduction : <span style="text-decoration: line-through;">'
  286. . $deal->find(
  287. 'span[class*=mute--text text--lineThrough]', 0
  288. )->plaintext
  289. . '</span>&nbsp;'
  290. . $deal->find('span[class=space--ml-1 size--all-l size--fromW3-xl]', 0)->plaintext
  291. . '</div>';
  292. } else {
  293. return '';
  294. }
  295. }
  296. /**
  297. * Get the Picture URL from a Deal if it exists
  298. * @return string String of the deal Picture URL
  299. */
  300. private function getImage($deal)
  301. {
  302. $selectorLazy = implode(
  303. ' ', /* Notice this is a space! */
  304. array(
  305. 'thread-image',
  306. 'width--all-auto',
  307. 'height--all-auto',
  308. 'imgFrame-img',
  309. 'cept-thread-img',
  310. 'img--dummy',
  311. 'js-lazy-img'
  312. )
  313. );
  314. $selectorPlain = implode(
  315. ' ', /* Notice this is a space! */
  316. array(
  317. 'thread-image',
  318. 'width--all-auto',
  319. 'height--all-auto',
  320. 'imgFrame-img',
  321. 'cept-thread-img'
  322. )
  323. );
  324. if($deal->find('img[class='. $selectorLazy .']', 0) != null) {
  325. return json_decode(
  326. html_entity_decode(
  327. $deal->find('img[class='. $selectorLazy .']', 0)
  328. ->getAttribute('data-lazy-img')))->{'src'};
  329. } else {
  330. return $deal->find('img[class='. $selectorPlain .']', 0 )->src;
  331. }
  332. }
  333. /**
  334. * Get the originating country from a Deal if it existsa
  335. * @return string String of the deal originating country
  336. */
  337. private function getExpedition($deal)
  338. {
  339. $selector = implode(
  340. ' ', /* Notice this is a space! */
  341. array(
  342. 'meta-ribbon',
  343. 'overflow--wrap-off',
  344. 'space--l-3',
  345. 'text--color-greyShade'
  346. )
  347. );
  348. if($deal->find('span[class='. $selector .']', 0) != null) {
  349. return '<div>'
  350. . $deal->find('span[class='. $selector .']', 0)->children(2)->plaintext
  351. . '</div>';
  352. } else {
  353. return '';
  354. }
  355. }
  356. /**
  357. * Transforms a French date into a timestam
  358. * @return int timestamp of the input date
  359. */
  360. private function parseDate($string)
  361. {
  362. $month_fr = array(
  363. 'janvier',
  364. 'février',
  365. 'mars',
  366. 'avril',
  367. 'mai',
  368. 'juin',
  369. 'juillet',
  370. 'août',
  371. 'septembre',
  372. 'octobre',
  373. 'novembre',
  374. 'décembre'
  375. );
  376. $month_en = array(
  377. 'January',
  378. 'February',
  379. 'March',
  380. 'April',
  381. 'May',
  382. 'June',
  383. 'July',
  384. 'August',
  385. 'September',
  386. 'October',
  387. 'November',
  388. 'December'
  389. );
  390. $date_str = trim(str_replace($month_fr, $month_en, $string));
  391. if(!preg_match('/[0-9]{4}/', $string)) {
  392. $date_str .= ' ' . date('Y');
  393. }
  394. $date_str .= ' 00:00';
  395. $date = DateTime::createFromFormat('j F Y H:i', $date_str);
  396. return $date->getTimestamp();
  397. }
  398. /**
  399. * Transforms a relate French date into a timestam
  400. * @return int timestamp of the input date
  401. */
  402. private function relativeDateToTimestamp($str) {
  403. $date = new DateTime();
  404. $search = array(
  405. 'il y a ',
  406. 'min',
  407. 'h',
  408. 'jour',
  409. 'jours',
  410. 'mois',
  411. 'ans',
  412. 'et '
  413. );
  414. $replace = array(
  415. '-',
  416. 'minute',
  417. 'hour',
  418. 'day',
  419. 'month',
  420. 'year',
  421. ''
  422. );
  423. $date->modify(str_replace($search, $replace, $str));
  424. return $date->getTimestamp();
  425. }
  426. public function getName(){
  427. switch($this->queriedContext) {
  428. case 'Recherche par Mot(s) clé(s)':
  429. return self::NAME . ' - Recherche : '. $this->getInput('q');
  430. break;
  431. case 'Deals par groupe':
  432. $values = self::PARAMETERS['Deals par groupe']['groupe']['values'];
  433. $groupe = array_search($this->getInput('groupe'), $values);
  434. return self::NAME . ' - Groupe : '. $groupe;
  435. break;
  436. default: // Return default value
  437. return self::NAME;
  438. }
  439. }
  440. }