InstructablesBridge.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. <?php
  2. /**
  3. * This class implements a bridge for http://www.instructables.com, supporting
  4. * general feeds and feeds by category. Instructables doesn't support HTTPS as
  5. * of now (23.06.2018), so all connections are insecure!
  6. *
  7. * Remarks:
  8. * - For some reason it is very important to have the category URI end with a
  9. * slash, otherwise the site defaults to the main category (i.e. Technology)!
  10. * If you need to update the categories list, enable the 'listCategories'
  11. * function (see comments below) and run the bridge with format=Html (see page
  12. * source)
  13. */
  14. class InstructablesBridge extends BridgeAbstract {
  15. const NAME = 'Instructables Bridge';
  16. const URI = 'http://www.instructables.com';
  17. const DESCRIPTION = 'Returns general feeds and feeds by category';
  18. const MAINTAINER = 'logmanoriginal';
  19. const PARAMETERS = array(
  20. 'Category' => array(
  21. 'category' => array(
  22. 'name' => 'Category',
  23. 'type' => 'list',
  24. 'required' => true,
  25. 'values' => array(
  26. 'Play' => array(
  27. 'All' => '/play/',
  28. 'KNEX' => '/play/knex/',
  29. 'Offbeat' => '/play/offbeat/',
  30. 'Lego' => '/play/lego/',
  31. 'Airsoft' => '/play/airsoft/',
  32. 'Card Games' => '/play/card-games/',
  33. 'Guitars' => '/play/guitars/',
  34. 'Instruments' => '/play/instruments/',
  35. 'Magic Tricks' => '/play/magic-tricks/',
  36. 'Minecraft' => '/play/minecraft/',
  37. 'Music' => '/play/music/',
  38. 'Nerf' => '/play/nerf/',
  39. 'Nintendo' => '/play/nintendo/',
  40. 'Office Supplies' => '/play/office-supplies/',
  41. 'Paintball' => '/play/paintball/',
  42. 'Paper Airplanes' => '/play/paper-airplanes/',
  43. 'Party Tricks' => '/play/party-tricks/',
  44. 'PlayStation' => '/play/playstation/',
  45. 'Pranks and Humor' => '/play/pranks-and-humor/',
  46. 'Puzzles' => '/play/puzzles/',
  47. 'Siege Engines' => '/play/siege-engines/',
  48. 'Sports' => '/play/sports/',
  49. 'Table Top' => '/play/table-top/',
  50. 'Toys' => '/play/toys/',
  51. 'Video Games' => '/play/video-games/',
  52. 'Wii' => '/play/wii/',
  53. 'Xbox' => '/play/xbox/',
  54. 'Yo-Yo' => '/play/yo-yo/',
  55. ),
  56. 'Craft' => array(
  57. 'All' => '/craft/',
  58. 'Art' => '/craft/art/',
  59. 'Sewing' => '/craft/sewing/',
  60. 'Paper' => '/craft/paper/',
  61. 'Jewelry' => '/craft/jewelry/',
  62. 'Fashion' => '/craft/fashion/',
  63. 'Books & Journals' => '/craft/books-and-journals/',
  64. 'Cards' => '/craft/cards/',
  65. 'Clay' => '/craft/clay/',
  66. 'Duct Tape' => '/craft/duct-tape/',
  67. 'Embroidery' => '/craft/embroidery/',
  68. 'Felt' => '/craft/felt/',
  69. 'Fiber Arts' => '/craft/fiber-arts/',
  70. 'Gifts & Wrapping' => '/craft/gifts-and-wrapping/',
  71. 'Knitting & Crocheting' => '/craft/knitting-and-crocheting/',
  72. 'Leather' => '/craft/leather/',
  73. 'Mason Jars' => '/craft/mason-jars/',
  74. 'No-Sew' => '/craft/no-sew/',
  75. 'Parties & Weddings' => '/craft/parties-and-weddings/',
  76. 'Print Making' => '/craft/print-making/',
  77. 'Soap' => '/craft/soap/',
  78. 'Wallets' => '/craft/wallets/',
  79. ),
  80. 'Technology' => array(
  81. 'All' => '/technology/',
  82. 'Electronics' => '/technology/electronics/',
  83. 'Arduino' => '/technology/arduino/',
  84. 'Photography' => '/technology/photography/',
  85. 'Leds' => '/technology/leds/',
  86. 'Science' => '/technology/science/',
  87. 'Reuse' => '/technology/reuse/',
  88. 'Apple' => '/technology/apple/',
  89. 'Computers' => '/technology/computers/',
  90. '3D Printing' => '/technology/3D-Printing/',
  91. 'Robots' => '/technology/robots/',
  92. 'Art' => '/technology/art/',
  93. 'Assistive Tech' => '/technology/assistive-technology/',
  94. 'Audio' => '/technology/audio/',
  95. 'Clocks' => '/technology/clocks/',
  96. 'CNC' => '/technology/cnc/',
  97. 'Digital Graphics' => '/technology/digital-graphics/',
  98. 'Gadgets' => '/technology/gadgets/',
  99. 'Kits' => '/technology/kits/',
  100. 'Laptops' => '/technology/laptops/',
  101. 'Lasers' => '/technology/lasers/',
  102. 'Linux' => '/technology/linux/',
  103. 'Microcontrollers' => '/technology/microcontrollers/',
  104. 'Microsoft' => '/technology/microsoft/',
  105. 'Mobile' => '/technology/mobile/',
  106. 'Raspberry Pi' => '/technology/raspberry-pi/',
  107. 'Remote Control' => '/technology/remote-control/',
  108. 'Sensors' => '/technology/sensors/',
  109. 'Software' => '/technology/software/',
  110. 'Soldering' => '/technology/soldering/',
  111. 'Speakers' => '/technology/speakers/',
  112. 'Steampunk' => '/technology/steampunk/',
  113. 'Tools' => '/technology/tools/',
  114. 'USB' => '/technology/usb/',
  115. 'Wearables' => '/technology/wearables/',
  116. 'Websites' => '/technology/websites/',
  117. 'Wireless' => '/technology/wireless/',
  118. ),
  119. 'Workshop' => array(
  120. 'All' => '/workshop/',
  121. 'Woodworking' => '/workshop/woodworking/',
  122. 'Tools' => '/workshop/tools/',
  123. 'Gardening' => '/workshop/gardening/',
  124. 'Cars' => '/workshop/cars/',
  125. 'Metalworking' => '/workshop/metalworking/',
  126. 'Cardboard' => '/workshop/cardboard/',
  127. 'Electric Vehicles' => '/workshop/electric-vehicles/',
  128. 'Energy' => '/workshop/energy/',
  129. 'Furniture' => '/workshop/furniture/',
  130. 'Home Improvement' => '/workshop/home-improvement/',
  131. 'Home Theater' => '/workshop/home-theater/',
  132. 'Hydroponics' => '/workshop/hydroponics/',
  133. 'Laser Cutting' => '/workshop/laser-cutting/',
  134. 'Lighting' => '/workshop/lighting/',
  135. 'Molds & Casting' => '/workshop/molds-and-casting/',
  136. 'Motorcycles' => '/workshop/motorcycles/',
  137. 'Organizing' => '/workshop/organizing/',
  138. 'Pallets' => '/workshop/pallets/',
  139. 'Repair' => '/workshop/repair/',
  140. 'Shelves' => '/workshop/shelves/',
  141. 'Solar' => '/workshop/solar/',
  142. 'Workbenches' => '/workshop/workbenches/',
  143. ),
  144. 'Home' => array(
  145. 'All' => '/home/',
  146. 'Halloween' => '/home/halloween/',
  147. 'Decorating' => '/home/decorating/',
  148. 'Organizing' => '/home/organizing/',
  149. 'Pets' => '/home/pets/',
  150. 'Life Hacks' => '/home/life-hacks/',
  151. 'Beauty' => '/home/beauty/',
  152. 'Christmas' => '/home/christmas/',
  153. 'Cleaning' => '/home/cleaning/',
  154. 'Education' => '/home/education/',
  155. 'Finances' => '/home/finances/',
  156. 'Gardening' => '/home/gardening/',
  157. 'Green' => '/home/green/',
  158. 'Health' => '/home/health/',
  159. 'Hiding Places' => '/home/hiding-places/',
  160. 'Holidays' => '/home/holidays/',
  161. 'Homesteading' => '/home/homesteading/',
  162. 'Kids' => '/home/kids/',
  163. 'Kitchen' => '/home/kitchen/',
  164. 'Life Skills' => '/home/life-skills/',
  165. 'Parenting' => '/home/parenting/',
  166. 'Pest Control' => '/home/pest-control/',
  167. 'Relationships' => '/home/relationships/',
  168. 'Reuse' => '/home/reuse/',
  169. 'Travel' => '/home/travel/',
  170. ),
  171. 'Outside' => array(
  172. 'All' => '/outside/',
  173. 'Bikes' => '/outside/bikes/',
  174. 'Survival' => '/outside/survival/',
  175. 'Backyard' => '/outside/backyard/',
  176. 'Beach' => '/outside/beach/',
  177. 'Birding' => '/outside/birding/',
  178. 'Boats' => '/outside/boats/',
  179. 'Camping' => '/outside/camping/',
  180. 'Climbing' => '/outside/climbing/',
  181. 'Fire' => '/outside/fire/',
  182. 'Fishing' => '/outside/fishing/',
  183. 'Hunting' => '/outside/hunting/',
  184. 'Kites' => '/outside/kites/',
  185. 'Knives' => '/outside/knives/',
  186. 'Knots' => '/outside/knots/',
  187. 'Paracord' => '/outside/paracord/',
  188. 'Rockets' => '/outside/rockets/',
  189. 'Skateboarding' => '/outside/skateboarding/',
  190. 'Snow' => '/outside/snow/',
  191. 'Water' => '/outside/water/',
  192. ),
  193. 'Food' => array(
  194. 'All' => '/food/',
  195. 'Dessert' => '/food/dessert/',
  196. 'Snacks & Appetizers' => '/food/snacks-and-appetizers/',
  197. 'Bacon' => '/food/bacon/',
  198. 'BBQ & Grilling' => '/food/bbq-and-grilling/',
  199. 'Beverages' => '/food/beverages/',
  200. 'Bread' => '/food/bread/',
  201. 'Breakfast' => '/food/breakfast/',
  202. 'Cake' => '/food/cake/',
  203. 'Candy' => '/food/candy/',
  204. 'Canning & Preserves' => '/food/canning-and-preserves/',
  205. 'Cocktails & Mocktails' => '/food/cocktails-and-mocktails/',
  206. 'Coffee' => '/food/coffee/',
  207. 'Cookies' => '/food/cookies/',
  208. 'Cupcakes' => '/food/cupcakes/',
  209. 'Homebrew' => '/food/homebrew/',
  210. 'Main Course' => '/food/main-course/',
  211. 'Pasta' => '/food/pasta/',
  212. 'Pie' => '/food/pie/',
  213. 'Pizza' => '/food/pizza/',
  214. 'Salad' => '/food/salad/',
  215. 'Sandwiches' => '/food/sandwiches/',
  216. 'Soups & Stews' => '/food/soups-and-stews/',
  217. 'Vegetarian & Vegan' => '/food/vegetarian-and-vegan/',
  218. ),
  219. 'Costumes' => array(
  220. 'All' => '/costumes/',
  221. 'Props' => '/costumes/props-and-accessories/',
  222. 'Animals' => '/costumes/animals/',
  223. 'Comics' => '/costumes/comics/',
  224. 'Fantasy' => '/costumes/fantasy/',
  225. 'For Kids' => '/costumes/for-kids/',
  226. 'For Pets' => '/costumes/for-pets/',
  227. 'Funny' => '/costumes/funny/',
  228. 'Games' => '/costumes/games/',
  229. 'Historic & Futuristic' => '/costumes/historic-and-futuristic/',
  230. 'Makeup' => '/costumes/makeup/',
  231. 'Masks' => '/costumes/masks/',
  232. 'Scary' => '/costumes/scary/',
  233. 'TV & Movies' => '/costumes/tv-and-movies/',
  234. 'Weapons & Armor' => '/costumes/weapons-and-armor/',
  235. )
  236. ),
  237. 'title' => 'Select your category (required)',
  238. 'defaultValue' => 'Technology'
  239. ),
  240. 'filter' => array(
  241. 'name' => 'Filter',
  242. 'type' => 'list',
  243. 'required' => true,
  244. 'values' => array(
  245. 'Featured' => ' ',
  246. 'Recent' => 'recent/',
  247. 'Popular' => 'popular/',
  248. 'Views' => 'views/',
  249. 'Contest Winners' => 'winners/'
  250. ),
  251. 'title' => 'Select a filter',
  252. 'defaultValue' => 'Featured'
  253. )
  254. )
  255. );
  256. private $uri;
  257. public function collectData() {
  258. // Enable the following line to get the category list (dev mode)
  259. // $this->listCategories();
  260. $this->uri = static::URI;
  261. switch($this->queriedContext) {
  262. case 'Category': $this->uri .= $this->getInput('category') . $this->getInput('filter');
  263. }
  264. $html = getSimpleHTMLDOM($this->uri)
  265. or returnServerError('Error loading category ' . $this->uri);
  266. foreach($html->find('ul.explore-covers-list li') as $cover) {
  267. $item = array();
  268. $item['uri'] = static::URI . $cover->find('a.cover-image', 0)->href;
  269. $item['title'] = $cover->find('.title', 0)->innertext;
  270. $item['author'] = $this->getCategoryAuthor($cover);
  271. $item['content'] = '<a href='
  272. . $item['uri']
  273. . '><img src='
  274. . $cover->find('a.cover-image img', 0)->src
  275. . '></a>';
  276. $image = str_replace('.RECTANGLE1', '.LARGE', $cover->find('a.cover-image img', 0)->src);
  277. $item['enclosures'] = [$image];
  278. $this->items[] = $item;
  279. }
  280. }
  281. public function getName() {
  282. if(!is_null($this->getInput('category'))
  283. && !is_null($this->getInput('filter'))) {
  284. foreach(self::PARAMETERS[$this->queriedContext]['category']['values'] as $key => $value) {
  285. $subcategory = array_search($this->getInput('category'), $value);
  286. if($subcategory !== false)
  287. break;
  288. }
  289. $filter = array_search(
  290. $this->getInput('filter'),
  291. self::PARAMETERS[$this->queriedContext]['filter']['values']
  292. );
  293. return $subcategory . ' (' . $filter . ') - ' . static::NAME;
  294. }
  295. return parent::getName();
  296. }
  297. public function getURI() {
  298. if(!is_null($this->getInput('category'))
  299. && !is_null($this->getInput('filter'))) {
  300. return $this->uri;
  301. }
  302. return parent::getURI();
  303. }
  304. /**
  305. * Returns a list of categories for development purposes (used to build the
  306. * parameters list)
  307. */
  308. private function listCategories(){
  309. // Use arbitrary category to receive full list
  310. $html = getSimpleHTMLDOM(self::URI . '/technology/');
  311. foreach($html->find('.channel a') as $channel) {
  312. $name = html_entity_decode(trim($channel->innertext));
  313. // Remove unwanted entities
  314. $name = str_replace("'", '', $name);
  315. $name = str_replace('&#39;', '', $name);
  316. $uri = $channel->href;
  317. $category = explode('/', $uri)[1];
  318. if(!isset($categories)
  319. || !array_key_exists($category, $categories)
  320. || !in_array($uri, $categories[$category]))
  321. $categories[$category][$name] = $uri;
  322. }
  323. // Build PHP array manually
  324. foreach($categories as $key => $value) {
  325. $name = ucfirst($key);
  326. echo "'{$name}' => array(\n";
  327. echo "\t'All' => '/{$key}/',\n";
  328. foreach($value as $name => $uri) {
  329. echo "\t'{$name}' => '{$uri}',\n";
  330. }
  331. echo "),\n";
  332. }
  333. die;
  334. }
  335. /**
  336. * Returns the author as anchor for a given cover.
  337. */
  338. private function getCategoryAuthor($cover) {
  339. return '<a href='
  340. . static::URI . $cover->find('span.author a', 0)->href
  341. . '>'
  342. . $cover->find('span.author a', 0)->innertext
  343. . '</a>';
  344. }
  345. }