|
@@ -0,0 +1,370 @@
|
|
|
+<?php
|
|
|
+/**
|
|
|
+* This class implements a bridge for http://www.instructables.com, supporting
|
|
|
+* general feeds and feeds by category. Instructables doesn't support HTTPS as
|
|
|
+* of now (23.06.2018), so all connections are insecure!
|
|
|
+*
|
|
|
+* Remarks:
|
|
|
+* - For some reason it is very important to have the category URI end with a
|
|
|
+* slash, otherwise the site defaults to the main category (i.e. Technology)!
|
|
|
+* If you need to update the categories list, enable the 'listCategories'
|
|
|
+* function (see comments below) and run the bridge with format=Html (see page
|
|
|
+* source)
|
|
|
+*/
|
|
|
+class InstructablesBridge extends BridgeAbstract {
|
|
|
+ const NAME = 'Instructables Bridge';
|
|
|
+ const URI = 'http://www.instructables.com';
|
|
|
+ const DESCRIPTION = 'Returns general feeds and feeds by category';
|
|
|
+ const MAINTAINER = 'logmanoriginal';
|
|
|
+ const PARAMETERS = array(
|
|
|
+ 'Category' => array(
|
|
|
+ 'category' => array(
|
|
|
+ 'name' => 'Category',
|
|
|
+ 'type' => 'list',
|
|
|
+ 'required' => true,
|
|
|
+ 'values' => array(
|
|
|
+ 'Play' => array(
|
|
|
+ 'All' => '/play/',
|
|
|
+ 'KNEX' => '/play/knex/',
|
|
|
+ 'Offbeat' => '/play/offbeat/',
|
|
|
+ 'Lego' => '/play/lego/',
|
|
|
+ 'Airsoft' => '/play/airsoft/',
|
|
|
+ 'Card Games' => '/play/card-games/',
|
|
|
+ 'Guitars' => '/play/guitars/',
|
|
|
+ 'Instruments' => '/play/instruments/',
|
|
|
+ 'Magic Tricks' => '/play/magic-tricks/',
|
|
|
+ 'Minecraft' => '/play/minecraft/',
|
|
|
+ 'Music' => '/play/music/',
|
|
|
+ 'Nerf' => '/play/nerf/',
|
|
|
+ 'Nintendo' => '/play/nintendo/',
|
|
|
+ 'Office Supplies' => '/play/office-supplies/',
|
|
|
+ 'Paintball' => '/play/paintball/',
|
|
|
+ 'Paper Airplanes' => '/play/paper-airplanes/',
|
|
|
+ 'Party Tricks' => '/play/party-tricks/',
|
|
|
+ 'PlayStation' => '/play/playstation/',
|
|
|
+ 'Pranks and Humor' => '/play/pranks-and-humor/',
|
|
|
+ 'Puzzles' => '/play/puzzles/',
|
|
|
+ 'Siege Engines' => '/play/siege-engines/',
|
|
|
+ 'Sports' => '/play/sports/',
|
|
|
+ 'Table Top' => '/play/table-top/',
|
|
|
+ 'Toys' => '/play/toys/',
|
|
|
+ 'Video Games' => '/play/video-games/',
|
|
|
+ 'Wii' => '/play/wii/',
|
|
|
+ 'Xbox' => '/play/xbox/',
|
|
|
+ 'Yo-Yo' => '/play/yo-yo/',
|
|
|
+ ),
|
|
|
+ 'Craft' => array(
|
|
|
+ 'All' => '/craft/',
|
|
|
+ 'Art' => '/craft/art/',
|
|
|
+ 'Sewing' => '/craft/sewing/',
|
|
|
+ 'Paper' => '/craft/paper/',
|
|
|
+ 'Jewelry' => '/craft/jewelry/',
|
|
|
+ 'Fashion' => '/craft/fashion/',
|
|
|
+ 'Books & Journals' => '/craft/books-and-journals/',
|
|
|
+ 'Cards' => '/craft/cards/',
|
|
|
+ 'Clay' => '/craft/clay/',
|
|
|
+ 'Duct Tape' => '/craft/duct-tape/',
|
|
|
+ 'Embroidery' => '/craft/embroidery/',
|
|
|
+ 'Felt' => '/craft/felt/',
|
|
|
+ 'Fiber Arts' => '/craft/fiber-arts/',
|
|
|
+ 'Gifts & Wrapping' => '/craft/gifts-and-wrapping/',
|
|
|
+ 'Knitting & Crocheting' => '/craft/knitting-and-crocheting/',
|
|
|
+ 'Leather' => '/craft/leather/',
|
|
|
+ 'Mason Jars' => '/craft/mason-jars/',
|
|
|
+ 'No-Sew' => '/craft/no-sew/',
|
|
|
+ 'Parties & Weddings' => '/craft/parties-and-weddings/',
|
|
|
+ 'Print Making' => '/craft/print-making/',
|
|
|
+ 'Soap' => '/craft/soap/',
|
|
|
+ 'Wallets' => '/craft/wallets/',
|
|
|
+ ),
|
|
|
+ 'Technology' => array(
|
|
|
+ 'All' => '/technology/',
|
|
|
+ 'Electronics' => '/technology/electronics/',
|
|
|
+ 'Arduino' => '/technology/arduino/',
|
|
|
+ 'Photography' => '/technology/photography/',
|
|
|
+ 'Leds' => '/technology/leds/',
|
|
|
+ 'Science' => '/technology/science/',
|
|
|
+ 'Reuse' => '/technology/reuse/',
|
|
|
+ 'Apple' => '/technology/apple/',
|
|
|
+ 'Computers' => '/technology/computers/',
|
|
|
+ '3D Printing' => '/technology/3D-Printing/',
|
|
|
+ 'Robots' => '/technology/robots/',
|
|
|
+ 'Art' => '/technology/art/',
|
|
|
+ 'Assistive Tech' => '/technology/assistive-technology/',
|
|
|
+ 'Audio' => '/technology/audio/',
|
|
|
+ 'Clocks' => '/technology/clocks/',
|
|
|
+ 'CNC' => '/technology/cnc/',
|
|
|
+ 'Digital Graphics' => '/technology/digital-graphics/',
|
|
|
+ 'Gadgets' => '/technology/gadgets/',
|
|
|
+ 'Kits' => '/technology/kits/',
|
|
|
+ 'Laptops' => '/technology/laptops/',
|
|
|
+ 'Lasers' => '/technology/lasers/',
|
|
|
+ 'Linux' => '/technology/linux/',
|
|
|
+ 'Microcontrollers' => '/technology/microcontrollers/',
|
|
|
+ 'Microsoft' => '/technology/microsoft/',
|
|
|
+ 'Mobile' => '/technology/mobile/',
|
|
|
+ 'Raspberry Pi' => '/technology/raspberry-pi/',
|
|
|
+ 'Remote Control' => '/technology/remote-control/',
|
|
|
+ 'Sensors' => '/technology/sensors/',
|
|
|
+ 'Software' => '/technology/software/',
|
|
|
+ 'Soldering' => '/technology/soldering/',
|
|
|
+ 'Speakers' => '/technology/speakers/',
|
|
|
+ 'Steampunk' => '/technology/steampunk/',
|
|
|
+ 'Tools' => '/technology/tools/',
|
|
|
+ 'USB' => '/technology/usb/',
|
|
|
+ 'Wearables' => '/technology/wearables/',
|
|
|
+ 'Websites' => '/technology/websites/',
|
|
|
+ 'Wireless' => '/technology/wireless/',
|
|
|
+ ),
|
|
|
+ 'Workshop' => array(
|
|
|
+ 'All' => '/workshop/',
|
|
|
+ 'Woodworking' => '/workshop/woodworking/',
|
|
|
+ 'Tools' => '/workshop/tools/',
|
|
|
+ 'Gardening' => '/workshop/gardening/',
|
|
|
+ 'Cars' => '/workshop/cars/',
|
|
|
+ 'Metalworking' => '/workshop/metalworking/',
|
|
|
+ 'Cardboard' => '/workshop/cardboard/',
|
|
|
+ 'Electric Vehicles' => '/workshop/electric-vehicles/',
|
|
|
+ 'Energy' => '/workshop/energy/',
|
|
|
+ 'Furniture' => '/workshop/furniture/',
|
|
|
+ 'Home Improvement' => '/workshop/home-improvement/',
|
|
|
+ 'Home Theater' => '/workshop/home-theater/',
|
|
|
+ 'Hydroponics' => '/workshop/hydroponics/',
|
|
|
+ 'Laser Cutting' => '/workshop/laser-cutting/',
|
|
|
+ 'Lighting' => '/workshop/lighting/',
|
|
|
+ 'Molds & Casting' => '/workshop/molds-and-casting/',
|
|
|
+ 'Motorcycles' => '/workshop/motorcycles/',
|
|
|
+ 'Organizing' => '/workshop/organizing/',
|
|
|
+ 'Pallets' => '/workshop/pallets/',
|
|
|
+ 'Repair' => '/workshop/repair/',
|
|
|
+ 'Shelves' => '/workshop/shelves/',
|
|
|
+ 'Solar' => '/workshop/solar/',
|
|
|
+ 'Workbenches' => '/workshop/workbenches/',
|
|
|
+ ),
|
|
|
+ 'Home' => array(
|
|
|
+ 'All' => '/home/',
|
|
|
+ 'Halloween' => '/home/halloween/',
|
|
|
+ 'Decorating' => '/home/decorating/',
|
|
|
+ 'Organizing' => '/home/organizing/',
|
|
|
+ 'Pets' => '/home/pets/',
|
|
|
+ 'Life Hacks' => '/home/life-hacks/',
|
|
|
+ 'Beauty' => '/home/beauty/',
|
|
|
+ 'Christmas' => '/home/christmas/',
|
|
|
+ 'Cleaning' => '/home/cleaning/',
|
|
|
+ 'Education' => '/home/education/',
|
|
|
+ 'Finances' => '/home/finances/',
|
|
|
+ 'Gardening' => '/home/gardening/',
|
|
|
+ 'Green' => '/home/green/',
|
|
|
+ 'Health' => '/home/health/',
|
|
|
+ 'Hiding Places' => '/home/hiding-places/',
|
|
|
+ 'Holidays' => '/home/holidays/',
|
|
|
+ 'Homesteading' => '/home/homesteading/',
|
|
|
+ 'Kids' => '/home/kids/',
|
|
|
+ 'Kitchen' => '/home/kitchen/',
|
|
|
+ 'Life Skills' => '/home/life-skills/',
|
|
|
+ 'Parenting' => '/home/parenting/',
|
|
|
+ 'Pest Control' => '/home/pest-control/',
|
|
|
+ 'Relationships' => '/home/relationships/',
|
|
|
+ 'Reuse' => '/home/reuse/',
|
|
|
+ 'Travel' => '/home/travel/',
|
|
|
+ ),
|
|
|
+ 'Outside' => array(
|
|
|
+ 'All' => '/outside/',
|
|
|
+ 'Bikes' => '/outside/bikes/',
|
|
|
+ 'Survival' => '/outside/survival/',
|
|
|
+ 'Backyard' => '/outside/backyard/',
|
|
|
+ 'Beach' => '/outside/beach/',
|
|
|
+ 'Birding' => '/outside/birding/',
|
|
|
+ 'Boats' => '/outside/boats/',
|
|
|
+ 'Camping' => '/outside/camping/',
|
|
|
+ 'Climbing' => '/outside/climbing/',
|
|
|
+ 'Fire' => '/outside/fire/',
|
|
|
+ 'Fishing' => '/outside/fishing/',
|
|
|
+ 'Hunting' => '/outside/hunting/',
|
|
|
+ 'Kites' => '/outside/kites/',
|
|
|
+ 'Knives' => '/outside/knives/',
|
|
|
+ 'Knots' => '/outside/knots/',
|
|
|
+ 'Paracord' => '/outside/paracord/',
|
|
|
+ 'Rockets' => '/outside/rockets/',
|
|
|
+ 'Skateboarding' => '/outside/skateboarding/',
|
|
|
+ 'Snow' => '/outside/snow/',
|
|
|
+ 'Water' => '/outside/water/',
|
|
|
+ ),
|
|
|
+ 'Food' => array(
|
|
|
+ 'All' => '/food/',
|
|
|
+ 'Dessert' => '/food/dessert/',
|
|
|
+ 'Snacks & Appetizers' => '/food/snacks-and-appetizers/',
|
|
|
+ 'Bacon' => '/food/bacon/',
|
|
|
+ 'BBQ & Grilling' => '/food/bbq-and-grilling/',
|
|
|
+ 'Beverages' => '/food/beverages/',
|
|
|
+ 'Bread' => '/food/bread/',
|
|
|
+ 'Breakfast' => '/food/breakfast/',
|
|
|
+ 'Cake' => '/food/cake/',
|
|
|
+ 'Candy' => '/food/candy/',
|
|
|
+ 'Canning & Preserves' => '/food/canning-and-preserves/',
|
|
|
+ 'Cocktails & Mocktails' => '/food/cocktails-and-mocktails/',
|
|
|
+ 'Coffee' => '/food/coffee/',
|
|
|
+ 'Cookies' => '/food/cookies/',
|
|
|
+ 'Cupcakes' => '/food/cupcakes/',
|
|
|
+ 'Homebrew' => '/food/homebrew/',
|
|
|
+ 'Main Course' => '/food/main-course/',
|
|
|
+ 'Pasta' => '/food/pasta/',
|
|
|
+ 'Pie' => '/food/pie/',
|
|
|
+ 'Pizza' => '/food/pizza/',
|
|
|
+ 'Salad' => '/food/salad/',
|
|
|
+ 'Sandwiches' => '/food/sandwiches/',
|
|
|
+ 'Soups & Stews' => '/food/soups-and-stews/',
|
|
|
+ 'Vegetarian & Vegan' => '/food/vegetarian-and-vegan/',
|
|
|
+ ),
|
|
|
+ 'Costumes' => array(
|
|
|
+ 'All' => '/costumes/',
|
|
|
+ 'Props' => '/costumes/props-and-accessories/',
|
|
|
+ 'Animals' => '/costumes/animals/',
|
|
|
+ 'Comics' => '/costumes/comics/',
|
|
|
+ 'Fantasy' => '/costumes/fantasy/',
|
|
|
+ 'For Kids' => '/costumes/for-kids/',
|
|
|
+ 'For Pets' => '/costumes/for-pets/',
|
|
|
+ 'Funny' => '/costumes/funny/',
|
|
|
+ 'Games' => '/costumes/games/',
|
|
|
+ 'Historic & Futuristic' => '/costumes/historic-and-futuristic/',
|
|
|
+ 'Makeup' => '/costumes/makeup/',
|
|
|
+ 'Masks' => '/costumes/masks/',
|
|
|
+ 'Scary' => '/costumes/scary/',
|
|
|
+ 'TV & Movies' => '/costumes/tv-and-movies/',
|
|
|
+ 'Weapons & Armor' => '/costumes/weapons-and-armor/',
|
|
|
+ )
|
|
|
+ ),
|
|
|
+ 'title' => 'Select your category (required)',
|
|
|
+ 'defaultValue' => 'Technology'
|
|
|
+ ),
|
|
|
+ 'filter' => array(
|
|
|
+ 'name' => 'Filter',
|
|
|
+ 'type' => 'list',
|
|
|
+ 'required' => true,
|
|
|
+ 'values' => array(
|
|
|
+ 'Featured' => ' ',
|
|
|
+ 'Recent' => 'recent/',
|
|
|
+ 'Popular' => 'popular/',
|
|
|
+ 'Views' => 'views/',
|
|
|
+ 'Contest Winners' => 'winners/'
|
|
|
+ ),
|
|
|
+ 'title' => 'Select a filter',
|
|
|
+ 'defaultValue' => 'Featured'
|
|
|
+ )
|
|
|
+ )
|
|
|
+ );
|
|
|
+
|
|
|
+ private $uri;
|
|
|
+
|
|
|
+ public function collectData() {
|
|
|
+ // Enable the following line to get the category list (dev mode)
|
|
|
+ // $this->listCategories();
|
|
|
+
|
|
|
+ $this->uri = static::URI;
|
|
|
+
|
|
|
+ switch($this->queriedContext) {
|
|
|
+ case 'Category': $this->uri .= $this->getInput('category') . $this->getInput('filter');
|
|
|
+ }
|
|
|
+
|
|
|
+ $html = getSimpleHTMLDOM($this->uri)
|
|
|
+ or returnServerError('Error loading category ' . $this->uri);
|
|
|
+
|
|
|
+ foreach($html->find('ul.explore-covers-list li') as $cover) {
|
|
|
+ $item = array();
|
|
|
+
|
|
|
+ $item['uri'] = static::URI . $cover->find('a.cover-image', 0)->href;
|
|
|
+ $item['title'] = $cover->find('.title', 0)->innertext;
|
|
|
+ $item['author'] = $this->getCategoryAuthor($cover);
|
|
|
+ $item['content'] = '<a href='
|
|
|
+ . $item['uri']
|
|
|
+ . '><img src='
|
|
|
+ . $cover->find('a.cover-image img', 0)->src
|
|
|
+ . '></a>';
|
|
|
+
|
|
|
+ $image = str_replace('.RECTANGLE1', '.LARGE', $cover->find('a.cover-image img', 0)->src);
|
|
|
+ $item['enclosures'] = [$image];
|
|
|
+
|
|
|
+ $this->items[] = $item;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public function getName() {
|
|
|
+ if(!is_null($this->getInput('category'))
|
|
|
+ && !is_null($this->getInput('filter'))) {
|
|
|
+ foreach(self::PARAMETERS[$this->queriedContext]['category']['values'] as $key => $value) {
|
|
|
+ $subcategory = array_search($this->getInput('category'), $value);
|
|
|
+
|
|
|
+ if($subcategory !== false)
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ $filter = array_search(
|
|
|
+ $this->getInput('filter'),
|
|
|
+ self::PARAMETERS[$this->queriedContext]['filter']['values']
|
|
|
+ );
|
|
|
+
|
|
|
+ return $subcategory . ' (' . $filter . ') - ' . static::NAME;
|
|
|
+ }
|
|
|
+
|
|
|
+ return parent::getName();
|
|
|
+ }
|
|
|
+
|
|
|
+ public function getURI() {
|
|
|
+ if(!is_null($this->getInput('category'))
|
|
|
+ && !is_null($this->getInput('filter'))) {
|
|
|
+ return $this->uri;
|
|
|
+ }
|
|
|
+
|
|
|
+ return parent::getURI();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Returns a list of categories for development purposes (used to build the
|
|
|
+ * parameters list)
|
|
|
+ */
|
|
|
+ private function listCategories(){
|
|
|
+ // Use arbitrary category to receive full list
|
|
|
+ $html = getSimpleHTMLDOM(self::URI . '/technology/');
|
|
|
+
|
|
|
+ foreach($html->find('.channel a') as $channel) {
|
|
|
+ $name = html_entity_decode(trim($channel->innertext));
|
|
|
+
|
|
|
+ // Remove unwanted entities
|
|
|
+ $name = str_replace("'", '', $name);
|
|
|
+ $name = str_replace(''', '', $name);
|
|
|
+
|
|
|
+ $uri = $channel->href;
|
|
|
+
|
|
|
+ $category = explode('/', $uri)[1];
|
|
|
+
|
|
|
+ if(!isset($categories)
|
|
|
+ || !array_key_exists($category, $categories)
|
|
|
+ || !in_array($uri, $categories[$category]))
|
|
|
+ $categories[$category][$name] = $uri;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Build PHP array manually
|
|
|
+ foreach($categories as $key => $value) {
|
|
|
+ $name = ucfirst($key);
|
|
|
+ echo "'{$name}' => array(\n";
|
|
|
+ echo "\t'All' => '/{$key}/',\n";
|
|
|
+ foreach($value as $name => $uri) {
|
|
|
+ echo "\t'{$name}' => '{$uri}',\n";
|
|
|
+ }
|
|
|
+ echo "),\n";
|
|
|
+ }
|
|
|
+
|
|
|
+ die;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Returns the author as anchor for a given cover.
|
|
|
+ */
|
|
|
+ private function getCategoryAuthor($cover) {
|
|
|
+ return '<a href='
|
|
|
+ . static::URI . $cover->find('span.author a', 0)->href
|
|
|
+ . '>'
|
|
|
+ . $cover->find('span.author a', 0)->innertext
|
|
|
+ . '</a>';
|
|
|
+ }
|
|
|
+}
|