diff options
Diffstat (limited to 'bridges/FurAffinityBridge.php')
-rw-r--r-- | bridges/FurAffinityBridge.php | 1857 |
1 files changed, 940 insertions, 917 deletions
diff --git a/bridges/FurAffinityBridge.php b/bridges/FurAffinityBridge.php index b5bd3ead..7e1dfd82 100644 --- a/bridges/FurAffinityBridge.php +++ b/bridges/FurAffinityBridge.php @@ -1,903 +1,925 @@ <?php -class FurAffinityBridge extends BridgeAbstract { - const NAME = 'FurAffinity Bridge'; - const URI = 'https://www.furaffinity.net'; - const CACHE_TIMEOUT = 300; // 5min - const DESCRIPTION = 'Returns posts from various sections of FurAffinity'; - const MAINTAINER = 'Roliga'; - const PARAMETERS = array( - 'Search' => array( - 'q' => array( - 'name' => 'Query', - 'required' => true, - 'exampleValue' => 'dog', - ), - 'rating-general' => array( - 'name' => 'General', - 'type' => 'checkbox', - 'defaultValue' => 'checked' - ), - 'rating-mature' => array( - 'name' => 'Mature', - 'type' => 'checkbox', - ), - 'rating-adult' => array( - 'name' => 'Adult', - 'type' => 'checkbox', - ), - 'range' => array( - 'name' => 'Time range', - 'type' => 'list', - 'values' => array( - 'A Day' => 'day', - '3 Days' => '3days', - 'A Week' => 'week', - 'A Month' => 'month', - 'All time' => 'all' - ), - 'defaultValue' => 'all' - ), - 'type-art' => array( - 'name' => 'Art', - 'type' => 'checkbox', - 'defaultValue' => 'checked' - ), - 'type-flash' => array( - 'name' => 'Flash', - 'type' => 'checkbox', - 'defaultValue' => 'checked' - ), - 'type-photo' => array( - 'name' => 'Photography', - 'type' => 'checkbox', - 'defaultValue' => 'checked' - ), - 'type-music' => array( - 'name' => 'Music', - 'type' => 'checkbox', - 'defaultValue' => 'checked' - ), - 'type-story' => array( - 'name' => 'Story', - 'type' => 'checkbox', - 'defaultValue' => 'checked' - ), - 'type-poetry' => array( - 'name' => 'Poetry', - 'type' => 'checkbox', - 'defaultValue' => 'checked' - ), - 'mode' => array( - 'name' => 'Match mode', - 'type' => 'list', - 'values' => array( - 'All of the words' => 'all', - 'Any of the words' => 'any', - 'Extended' => 'extended' - ), - 'defaultValue' => 'extended' - ), - 'limit' => array( - 'name' => 'Limit', - 'type' => 'number', - 'required' => true, - 'defaultValue' => 10, - 'title' => 'Limit number of submissions to return. -1 for unlimited.' - ), - 'full' => array( - 'name' => 'Full view', - 'title' => 'Include description, tags, date and larger image in article. Uses more bandwidth.', - 'type' => 'checkbox', - 'defaultValue' => 'checked' - ), - 'cache' => array( - 'name' => 'Cache submission pages', - 'title' => 'Reduces requests to FA when Full view is enabled. Changes to submission details may be delayed.', - 'type' => 'checkbox', - 'defaultValue' => 'checked' - ) - ), - 'Browse' => array( - 'cat' => array( - 'name' => 'Category', - 'type' => 'list', - 'values' => array( - 'Visual Art' => array( - 'All' => 1, - 'Artwork (Digital)' => 2, - 'Artwork (Traditional)' => 3, - 'Cellshading' => 4, - 'Crafting' => 5, - 'Designs' => 6, - 'Flash' => 7, - 'Fursuiting' => 8, - 'Icons' => 9, - 'Mosaics' => 10, - 'Photography' => 11, - 'Sculpting' => 12 - ), - 'Readable Art' => array( - 'Story' => 13, - 'Poetry' => 14, - 'Prose' => 15 - ), - 'Audio Art' => array( - 'Music' => 16, - 'Podcasts' => 17 - ), - 'Downloadable' => array( - 'Skins' => 18, - 'Handhelds' => 19, - 'Resources' => 20 - ), - 'Other Stuff' => array( - 'Adoptables' => 21, - 'Auctions' => 22, - 'Contests' => 23, - 'Current Events' => 24, - 'Desktops' => 25, - 'Stockart' => 26, - 'Screenshots' => 27, - 'Scraps' => 28, - 'Wallpaper' => 29, - 'YCH / Sale' => 30, - 'Other' => 31 - ) - ), - 'defaultValue' => 1 - ), - 'atype' => array( - 'name' => 'Type', - 'type' => 'list', - 'values' => array( - 'General Things' => array( - 'All' => 1, - 'Abstract' => 2, - 'Animal related (non-anthro)' => 3, - 'Anime' => 4, - 'Comics' => 5, - 'Doodle' => 6, - 'Fanart' => 7, - 'Fantasy' => 8, - 'Human' => 9, - 'Portraits' => 10, - 'Scenery' => 11, - 'Still Life' => 12, - 'Tutorials' => 13, - 'Miscellaneous' => 14 - ), - 'Fetish / Furry specialty' => array( - 'Baby fur' => 101, - 'Bondage' => 102, - 'Digimon' => 103, - 'Fat Furs' => 104, - 'Fetish Other' => 105, - 'Fursuit' => 106, - 'Gore / Macabre Art' => 119, - 'Hyper' => 107, - 'Inflation' => 108, - 'Macro / Micro' => 109, - 'Muscle' => 110, - 'My Little Pony / Brony' => 111, - 'Paw' => 112, - 'Pokemon' => 113, - 'Pregnancy' => 114, - 'Sonic' => 115, - 'Transformation' => 116, - 'Vore' => 117, - 'Water Sports' => 118, - 'General Furry Art' => 100 - ), - 'Music' => array( - 'Techno' => 201, - 'Trance' => 202, - 'House' => 203, - '90s' => 204, - '80s' => 205, - '70s' => 206, - '60s' => 207, - 'Pre-60s' => 208, - 'Classical' => 209, - 'Game Music' => 210, - 'Rock' => 211, - 'Pop' => 212, - 'Rap' => 213, - 'Industrial' => 214, - 'Other Music' => 200 - ) - ), - 'defaultValue' => 1 - ), - 'species' => array( - 'name' => 'Species', - 'type' => 'list', - 'values' => array( - 'Unspecified / Any' => 1, - 'Amphibian' => array( - 'Frog' => 1001, - 'Newt' => 1002, - 'Salamander' => 1003, - 'Amphibian (Other)' => 1000 - ), - 'Aquatic' => array( - 'Cephalopod' => 2001, - 'Dolphin' => 2002, - 'Fish' => 2005, - 'Porpoise' => 2004, - 'Seal' => 6068, - 'Shark' => 2006, - 'Whale' => 2003, - 'Aquatic (Other)' => 2000 - ), - 'Avian' => array( - 'Corvid' => 3001, - 'Crow' => 3002, - 'Duck' => 3003, - 'Eagle' => 3004, - 'Falcon' => 3005, - 'Goose' => 3006, - 'Gryphon' => 3007, - 'Hawk' => 3008, - 'Owl' => 3009, - 'Phoenix' => 3010, - 'Swan' => 3011, - 'Avian (Other)' => 3000 - ), - 'Bears & Ursines' => array( - 'Bear' => 6002 - ), - 'Camelids' => array( - 'Camel' => 6074, - 'Llama' => 6036 - ), - 'Canines & Lupines' => array( - 'Coyote' => 6008, - 'Doberman' => 6009, - 'Dog' => 6010, - 'Dingo' => 6011, - 'German Shepherd' => 6012, - 'Jackal' => 6013, - 'Husky' => 6014, - 'Wolf' => 6016, - 'Canine (Other)' => 6017 - ), - 'Cervines' => array( - 'Cervine (Other)' => 6018 - ), - 'Cows & Bovines' => array( - 'Antelope' => 6004, - 'Cows' => 6003, - 'Gazelle' => 6005, - 'Goat' => 6006, - 'Bovines (General)' => 6007 - ), - 'Dragons' => array( - 'Eastern Dragon' => 4001, - 'Hydra' => 4002, - 'Serpent' => 4003, - 'Western Dragon' => 4004, - 'Wyvern' => 4005, - 'Dragon (Other)' => 4000 - ), - 'Equestrians' => array( - 'Donkey' => 6019, - 'Horse' => 6034, - 'Pony' => 6073, - 'Zebra' => 6071 - ), - 'Exotic & Mythicals' => array( - 'Argonian' => 5002, - 'Chakat' => 5003, - 'Chocobo' => 5004, - 'Citra' => 5005, - 'Crux' => 5006, - 'Daemon' => 5007, - 'Digimon' => 5008, - 'Dracat' => 5009, - 'Draenei' => 5010, - 'Elf' => 5011, - 'Gargoyle' => 5012, - 'Iksar' => 5013, - 'Kaiju/Monster' => 5015, - 'Langurhali' => 5014, - 'Moogle' => 5017, - 'Naga' => 5016, - 'Orc' => 5018, - 'Pokemon' => 5019, - 'Satyr' => 5020, - 'Sergal' => 5021, - 'Tanuki' => 5022, - 'Unicorn' => 5023, - 'Xenomorph' => 5024, - 'Alien (Other)' => 5001, - 'Exotic (Other)' => 5000 - ), - 'Felines' => array( - 'Domestic Cat' => 6020, - 'Cheetah' => 6021, - 'Cougar' => 6022, - 'Jaguar' => 6023, - 'Leopard' => 6024, - 'Lion' => 6025, - 'Lynx' => 6026, - 'Ocelot' => 6027, - 'Panther' => 6028, - 'Tiger' => 6029, - 'Feline (Other)' => 6030 - ), - 'Insects' => array( - 'Arachnid' => 8000, - 'Mantid' => 8004, - 'Scorpion' => 8005, - 'Insect (Other)' => 8003 - ), - 'Mammals (Other)' => array( - 'Bat' => 6001, - 'Giraffe' => 6031, - 'Hedgehog' => 6032, - 'Hippopotamus' => 6033, - 'Hyena' => 6035, - 'Panda' => 6052, - 'Pig/Swine' => 6053, - 'Rabbit/Hare' => 6059, - 'Raccoon' => 6060, - 'Red Panda' => 6062, - 'Meerkat' => 6043, - 'Mongoose' => 6044, - 'Rhinoceros' => 6063, - 'Mammals (Other)' => 6000 - ), - 'Marsupials' => array( - 'Opossum' => 6037, - 'Kangaroo' => 6038, - 'Koala' => 6039, - 'Quoll' => 6040, - 'Wallaby' => 6041, - 'Marsupial (Other)' => 6042 - ), - 'Mustelids' => array( - 'Badger' => 6045, - 'Ferret' => 6046, - 'Mink' => 6048, - 'Otter' => 6047, - 'Skunk' => 6069, - 'Weasel' => 6049, - 'Mustelid (Other)' => 6051 - ), - 'Primates' => array( - 'Gorilla' => 6054, - 'Human' => 6055, - 'Lemur' => 6056, - 'Monkey' => 6057, - 'Primate (Other)' => 6058 - ), - 'Reptillian' => array( - 'Alligator & Crocodile' => 7001, - 'Gecko' => 7003, - 'Iguana' => 7004, - 'Lizard' => 7005, - 'Snakes & Serpents' => 7006, - 'Turtle' => 7007, - 'Reptilian (Other)' => 7000 - ), - 'Rodents' => array( - 'Beaver' => 6064, - 'Mouse' => 6065, - 'Rat' => 6061, - 'Squirrel' => 6070, - 'Rodent (Other)' => 6067 - ), - 'Vulpines' => array( - 'Fennec' => 6072, - 'Fox' => 6075, - 'Vulpine (Other)' => 6015 - ), - 'Other' => array( - 'Dinosaur' => 8001, - 'Wolverine' => 6050 - ) - ), - 'defaultValue' => 1 - ), - 'gender' => array( - 'name' => 'Gender', - 'type' => 'list', - 'values' => array( - 'Any' => 0, - 'Male' => 2, - 'Female' => 3, - 'Herm' => 4, - 'Transgender' => 5, - 'Multiple characters' => 6, - 'Other / Not Specified' => 7 - ), - 'defaultValue' => 0 - ), - 'rating_general' => array( - 'name' => 'General', - 'type' => 'checkbox', - 'defaultValue' => 'checked' - ), - 'rating_mature' => array( - 'name' => 'Mature', - 'type' => 'checkbox', - ), - 'rating_adult' => array( - 'name' => 'Adult', - 'type' => 'checkbox', - ), - 'limit-browse' => array( - 'name' => 'Limit', - 'type' => 'number', - 'required' => true, - 'defaultValue' => 10, - 'title' => 'Limit number of submissions to return. -1 for unlimited.' - ), - 'full' => array( - 'name' => 'Full view', - 'title' => 'Include description, tags, date and larger image in article. Uses more bandwidth.', - 'type' => 'checkbox', - 'defaultValue' => 'checked' - ), - 'cache' => array( - 'name' => 'Cache submission pages', - 'title' => 'Reduces requests to FA when Full view is enabled. Changes to submission details may be delayed.', - 'type' => 'checkbox', - 'defaultValue' => 'checked' - ) - - ), - 'Journals' => array( - 'username-journals' => array( - 'name' => 'Username', - 'required' => true, - 'exampleValue' => 'dhw', - 'title' => 'Lowercase username as seen in URLs' - ), - 'limit' => array( - 'name' => 'Limit', - 'type' => 'number', - 'defaultValue' => -1, - 'title' => 'Limit number of journals to return. -1 for unlimited.' - ) - - ), - 'Single Journal' => array( - 'journal-id' => array( - 'name' => 'Journal ID', - 'required' => true, - 'exampleValue' => '10008853', - 'type' => 'number', - 'title' => 'Number seen in journal URL' - ) - ), - 'Gallery' => array( - 'username-gallery' => array( - 'name' => 'Username', - 'required' => true, - 'exampleValue' => 'dhw', - 'title' => 'Lowercase username as seen in URLs' - ), - 'limit' => array( - 'name' => 'Limit', - 'type' => 'number', - 'required' => true, - 'defaultValue' => 10, - 'title' => 'Limit number of submissions to return. -1 for unlimited.' - ), - 'full' => array( - 'name' => 'Full view', - 'title' => 'Include description, tags, date and larger image in article. Uses more bandwidth.', - 'type' => 'checkbox', - 'defaultValue' => 'checked' - ), - 'cache' => array( - 'name' => 'Cache submission pages', - 'title' => 'Reduces requests to FA when Full view is enabled. Changes to submission details may be delayed.', - 'type' => 'checkbox', - 'defaultValue' => 'checked' - ) - ), - 'Scraps' => array( - 'username-scraps' => array( - 'name' => 'Username', - 'required' => true, - 'exampleValue' => 'dhw', - 'title' => 'Lowercase username as seen in URLs' - ), - 'limit' => array( - 'name' => 'Limit', - 'type' => 'number', - 'required' => true, - 'defaultValue' => 10, - 'title' => 'Limit number of submissions to return. -1 for unlimited.' - ), - 'full' => array( - 'name' => 'Full view', - 'title' => 'Include description, tags, date and larger image in article. Uses more bandwidth.', - 'type' => 'checkbox', - 'defaultValue' => 'checked' - ), - 'cache' => array( - 'name' => 'Cache submission pages', - 'title' => 'Reduces requests to FA when Full view is enabled. Changes to submission details may be delayed.', - 'type' => 'checkbox', - 'defaultValue' => 'checked' - ) - ), - 'Favorites' => array( - 'username-favorites' => array( - 'name' => 'Username', - 'required' => true, - 'exampleValue' => 'dhw', - 'title' => 'Lowercase username as seen in URLs' - ), - 'limit' => array( - 'name' => 'Limit', - 'type' => 'number', - 'required' => true, - 'defaultValue' => 10, - 'title' => 'Limit number of submissions to return. -1 for unlimited.' - ), - 'full' => array( - 'name' => 'Full view', - 'title' => 'Include description, tags, date and larger image in article. Uses more bandwidth.', - 'type' => 'checkbox', - 'defaultValue' => 'checked' - ), - 'cache' => array( - 'name' => 'Cache submission pages', - 'title' => 'Reduces requests to FA when Full view is enabled. Changes to submission details may be delayed.', - 'type' => 'checkbox', - 'defaultValue' => 'checked' - ) - ), - 'Gallery Folder' => array( - 'username-folder' => array( - 'name' => 'Username', - 'required' => true, - 'exampleValue' => 'kopk', - 'title' => 'Lowercase username as seen in URLs' - ), - 'folder-id' => array( - 'name' => 'Folder ID', - 'required' => true, - 'exampleValue' => '1031990', - 'type' => 'number', - 'title' => 'Number seen in folder URL' - ), - 'limit' => array( - 'name' => 'Limit', - 'type' => 'number', - 'required' => true, - 'defaultValue' => 10, - 'title' => 'Limit number of submissions to return. -1 for unlimited.' - ), - 'full' => array( - 'name' => 'Full view', - 'title' => 'Include description, tags, date and larger image in article. Uses more bandwidth.', - 'type' => 'checkbox', - 'defaultValue' => 'checked' - ), - 'cache' => array( - 'name' => 'Cache submission pages', - 'title' => 'Reduces requests to FA when Full view is enabled. Changes to submission details may be delayed.', - 'type' => 'checkbox', - 'defaultValue' => 'checked' - ) - ) - ); - - /* - * This was aquired by creating a new user on FA then - * extracting the cookie from the browsers dev console. - */ - const FA_AUTH_COOKIE = 'b=4ce65691-b50f-4742-a990-bf28d6de16ee; a=ca6e4566-9d81-4263-9444-653b142e35f8'; - - public function detectParameters($url) { - $params = array(); - - // Single journal - $regex = '/^(https?:\/\/)?(www\.)?furaffinity.net\/journal\/(\d+)/'; - if(preg_match($regex, $url, $matches) > 0) { - $params['journal-id'] = urldecode($matches[3]); - return $params; - } - - // Journals - $regex = '/^(https?:\/\/)?(www\.)?furaffinity.net\/journals\/([^\/&?\n]+)/'; - if(preg_match($regex, $url, $matches) > 0) { - $params['username-journals'] = urldecode($matches[3]); - return $params; - } - - // Gallery folder - $regex = '/^(https?:\/\/)?(www\.)?furaffinity.net\/gallery\/([^\/&?\n]+)\/folder\/(\d+)/'; - if(preg_match($regex, $url, $matches) > 0) { - $params['username-folder'] = urldecode($matches[3]); - $params['folder-id'] = urldecode($matches[4]); - $params['full'] = 'on'; - return $params; - } - - // Gallery (must be after gallery folder) - $regex = '/^(https?:\/\/)?(www\.)?furaffinity.net\/(gallery|scraps|favorites)\/([^\/&?\n]+)/'; - if(preg_match($regex, $url, $matches) > 0) { - $params['username-' . $matches[3]] = urldecode($matches[4]); - $params['full'] = 'on'; - return $params; - } - - return null; - } - - public function getName() { - switch($this->queriedContext) { - case 'Search': - return 'Search For ' - . $this->getInput('q'); - case 'Browse': - return 'Browse'; - case 'Journals': - return $this->getInput('username-journals'); - case 'Single Journal': - return 'Journal ' - . $this->getInput('journal-id'); - case 'Gallery': - return $this->getInput('username-gallery'); - case 'Scraps': - return $this->getInput('username-scraps'); - case 'Favorites': - return $this->getInput('username-favorites'); - case 'Gallery Folder': - return $this->getInput('username-folder') - . '\'s Folder ' - . $this->getInput('folder-id'); - default: return parent::getName(); - } - } - - public function getDescription() { - switch($this->queriedContext) { - case 'Search': - return 'FurAffinity Search For ' - . $this->getInput('q'); - case 'Browse': - return 'FurAffinity Browse'; - case 'Journals': - return 'FurAffinity Journals By ' - . $this->getInput('username-journals'); - case 'Single Journal': - return 'FurAffinity Journal ' - . $this->getInput('journal-id'); - case 'Gallery': - return 'FurAffinity Gallery By ' - . $this->getInput('username-gallery'); - case 'Scraps': - return 'FurAffinity Scraps By ' - . $this->getInput('username-scraps'); - case 'Favorites': - return 'FurAffinity Favorites By ' - . $this->getInput('username-favorites'); - case 'Gallery Folder': - return 'FurAffinity Gallery Folder ' - . $this->getInput('folder-id') - . ' By ' - . $this->getInput('username-folder'); - default: return parent::getDescription(); - } - } - - public function getURI() { - switch($this->queriedContext) { - case 'Search': - return SELF::URI - . '/search'; - case 'Browse': - return SELF::URI - . '/browse'; - case 'Journals': - return SELF::URI - . '/journals/' - . $this->getInput('username-journals'); - case 'Single Journal': - return SELF::URI - . '/journal/' - . $this->getInput('journal-id'); - case 'Gallery': - return SELF::URI - . '/gallery/' - . $this->getInput('username-gallery'); - case 'Scraps': - return SELF::URI - . '/scraps/' - . $this->getInput('username-scraps'); - case 'Favorites': - return SELF::URI - . '/favorites/' - . $this->getInput('username-favorites'); - case 'Gallery Folder': - return SELF::URI - . '/gallery/' - . $this->getInput('username-folder') - . '/folder/' - . $this->getInput('folder-id'); - default: return parent::getURI(); - } - } - - public function collectData() { - switch($this->queriedContext) { - case 'Search': - $data = array( - 'q' => $this->getInput('q'), - 'perpage' => 72, - 'rating-general' => ($this->getInput('rating-general') === true ? 'on' : 0), - 'rating-mature' => ($this->getInput('rating-mature') === true ? 'on' : 0), - 'rating-adult' => ($this->getInput('rating-adult') === true ? 'on' : 0), - 'range' => $this->getInput('range'), - 'type-art' => ($this->getInput('type-art') === true ? 'on' : 0), - 'type-flash' => ($this->getInput('type-flash') === true ? 'on' : 0), - 'type-photo' => ($this->getInput('type-photo') === true ? 'on' : 0), - 'type-music' => ($this->getInput('type-music') === true ? 'on' : 0), - 'type-story' => ($this->getInput('type-story') === true ? 'on' : 0), - 'type-poetry' => ($this->getInput('type-poetry') === true ? 'on' : 0), - 'mode' => $this->getInput('mode') - ); - $html = $this->postFASimpleHTMLDOM($data); - $limit = (is_int($this->getInput('limit')) ? $this->getInput('limit') : 10); - $this->itemsFromSubmissionList($html, $limit); - break; - case 'Browse': - $data = array( - 'cat' => $this->getInput('cat'), - 'atype' => $this->getInput('atype'), - 'species' => $this->getInput('species'), - 'gender' => $this->getInput('gender'), - 'perpage' => 72, - 'rating_general' => ($this->getInput('rating_general') === true ? 'on' : 0), - 'rating_mature' => ($this->getInput('rating_mature') === true ? 'on' : 0), - 'rating_adult' => ($this->getInput('rating_adult') === true ? 'on' : 0) - ); - $html = $this->postFASimpleHTMLDOM($data); - $limit = (is_int($this->getInput('limit-browse')) ? $this->getInput('limit-browse') : 10); - $this->itemsFromSubmissionList($html, $limit); - break; - case 'Journals': - $html = $this->getFASimpleHTMLDOM($this->getURI()); - $limit = (is_int($this->getInput('limit')) ? $this->getInput('limit') : -1); - $this->itemsFromJournalList($html, $limit); - break; - case 'Single Journal': - $html = $this->getFASimpleHTMLDOM($this->getURI()); - $this->itemsFromJournal($html); - break; - case 'Gallery': - case 'Scraps': - case 'Favorites': - case 'Gallery Folder': - $html = $this->getFASimpleHTMLDOM($this->getURI()); - $limit = (is_int($this->getInput('limit')) ? $this->getInput('limit') : 10); - $this->itemsFromSubmissionList($html, $limit); - break; - } - } - - private function postFASimpleHTMLDOM($data) { - $opts = array( - CURLOPT_CUSTOMREQUEST => 'POST', - CURLOPT_POSTFIELDS => http_build_query($data) - ); - $header = array( - 'Host: ' . parse_url(self::URI, PHP_URL_HOST), - 'Content-Type: application/x-www-form-urlencoded', - 'Cookie: ' . self::FA_AUTH_COOKIE - ); - - $html = getSimpleHTMLDOM($this->getURI(), $header, $opts); - $html = defaultLinkTo($html, $this->getURI()); - - return $html; - } - - private function getFASimpleHTMLDOM($url, $cache = false) { - $header = array( - 'Cookie: ' . self::FA_AUTH_COOKIE - ); - - if($cache) { - $html = getSimpleHTMLDOMCached($url, 86400, $header); // 24 hours - } else { - $html = getSimpleHTMLDOM($url, $header); - } - - $html = defaultLinkTo($html, $url); - - return $html; - } - - private function itemsFromJournalList($html, $limit) { - foreach($html->find('table[id^=jid:]') as $journal) { - # allows limit = -1 to mean 'unlimited' - if($limit-- === 0) break; - - $item = array(); - - $this->setReferrerPolicy($journal); - - $item['uri'] = $journal->find('a', 0)->href; - $item['title'] = html_entity_decode($journal->find('a', 0)->plaintext); - $item['author'] = $this->getInput('username-journals'); - $item['timestamp'] = strtotime( - $journal->find('span.popup_date', 0)->plaintext); - $item['content'] = $journal - ->find('.alt1 table div.no_overflow', 0) - ->innertext; - - $this->items[] = $item; - } - } - - private function itemsFromJournal($html) { - $this->setReferrerPolicy($html); - $item = array(); - - $item['uri'] = $this->getURI(); - - $title = $html->find('.journal-title-box .no_overflow', 0)->plaintext; - $title = html_entity_decode($title); - $title = trim($title, " \t\n\r\0\x0B" . chr(0xC2) . chr(0xA0)); - $item['title'] = $title; - - $item['author'] = $html->find('.journal-title-box a', 0)->plaintext; - $item['timestamp'] = strtotime( - $html->find('.journal-title-box span.popup_date', 0)->plaintext); - $item['content'] = $html->find('.journal-body', 0)->innertext; - - $this->items[] = $item; - } - - private function itemsFromSubmissionList($html, $limit) { - $cache = ($this->getInput('cache') === true); - - foreach($html->find('section.gallery figure') as $figure) { - # allows limit = -1 to mean 'unlimited' - if($limit-- === 0) break; - - $item = array(); - - $submissionURL = $figure->find('b u a', 0)->href; - $imgURL = 'https:' . $figure->find('b u a img', 0)->src; - - $item['uri'] = $submissionURL; - $item['title'] = html_entity_decode( - $figure->find('figcaption p a[href*=/view/]', 0)->title); - $item['author'] = $figure->find('figcaption p a[href*=/user/]', 0)->title; - - if($this->getInput('full') === true) { - $submissionHTML = $this->getFASimpleHTMLDOM($submissionURL, $cache); - - $stats = $submissionHTML->find('.stats-container', 0); - $item['timestamp'] = strtotime($stats->find('.popup_date', 0)->title); - $item['enclosures'] = array( - $submissionHTML->find('.actions a[href^=https://d.facdn]', 0)->href - ); - foreach($stats->find('#keywords a') as $keyword) { - $item['categories'][] = $keyword->plaintext; - } - - $previewSrc = $submissionHTML->find('#submissionImg', 0) - ->{'data-preview-src'}; - if($previewSrc) { - $imgURL = 'https:' . $previewSrc; - } - $description = $submissionHTML - ->find('.maintable .maintable tr td.alt1', -1); - $this->setReferrerPolicy($description); - $description = $description->innertext; - - $item['content'] = <<<EOD +class FurAffinityBridge extends BridgeAbstract +{ + const NAME = 'FurAffinity Bridge'; + const URI = 'https://www.furaffinity.net'; + const CACHE_TIMEOUT = 300; // 5min + const DESCRIPTION = 'Returns posts from various sections of FurAffinity'; + const MAINTAINER = 'Roliga'; + const PARAMETERS = [ + 'Search' => [ + 'q' => [ + 'name' => 'Query', + 'required' => true, + 'exampleValue' => 'dog', + ], + 'rating-general' => [ + 'name' => 'General', + 'type' => 'checkbox', + 'defaultValue' => 'checked' + ], + 'rating-mature' => [ + 'name' => 'Mature', + 'type' => 'checkbox', + ], + 'rating-adult' => [ + 'name' => 'Adult', + 'type' => 'checkbox', + ], + 'range' => [ + 'name' => 'Time range', + 'type' => 'list', + 'values' => [ + 'A Day' => 'day', + '3 Days' => '3days', + 'A Week' => 'week', + 'A Month' => 'month', + 'All time' => 'all' + ], + 'defaultValue' => 'all' + ], + 'type-art' => [ + 'name' => 'Art', + 'type' => 'checkbox', + 'defaultValue' => 'checked' + ], + 'type-flash' => [ + 'name' => 'Flash', + 'type' => 'checkbox', + 'defaultValue' => 'checked' + ], + 'type-photo' => [ + 'name' => 'Photography', + 'type' => 'checkbox', + 'defaultValue' => 'checked' + ], + 'type-music' => [ + 'name' => 'Music', + 'type' => 'checkbox', + 'defaultValue' => 'checked' + ], + 'type-story' => [ + 'name' => 'Story', + 'type' => 'checkbox', + 'defaultValue' => 'checked' + ], + 'type-poetry' => [ + 'name' => 'Poetry', + 'type' => 'checkbox', + 'defaultValue' => 'checked' + ], + 'mode' => [ + 'name' => 'Match mode', + 'type' => 'list', + 'values' => [ + 'All of the words' => 'all', + 'Any of the words' => 'any', + 'Extended' => 'extended' + ], + 'defaultValue' => 'extended' + ], + 'limit' => [ + 'name' => 'Limit', + 'type' => 'number', + 'required' => true, + 'defaultValue' => 10, + 'title' => 'Limit number of submissions to return. -1 for unlimited.' + ], + 'full' => [ + 'name' => 'Full view', + 'title' => 'Include description, tags, date and larger image in article. Uses more bandwidth.', + 'type' => 'checkbox', + 'defaultValue' => 'checked' + ], + 'cache' => [ + 'name' => 'Cache submission pages', + 'title' => 'Reduces requests to FA when Full view is enabled. Changes to submission details may be delayed.', + 'type' => 'checkbox', + 'defaultValue' => 'checked' + ] + ], + 'Browse' => [ + 'cat' => [ + 'name' => 'Category', + 'type' => 'list', + 'values' => [ + 'Visual Art' => [ + 'All' => 1, + 'Artwork (Digital)' => 2, + 'Artwork (Traditional)' => 3, + 'Cellshading' => 4, + 'Crafting' => 5, + 'Designs' => 6, + 'Flash' => 7, + 'Fursuiting' => 8, + 'Icons' => 9, + 'Mosaics' => 10, + 'Photography' => 11, + 'Sculpting' => 12 + ], + 'Readable Art' => [ + 'Story' => 13, + 'Poetry' => 14, + 'Prose' => 15 + ], + 'Audio Art' => [ + 'Music' => 16, + 'Podcasts' => 17 + ], + 'Downloadable' => [ + 'Skins' => 18, + 'Handhelds' => 19, + 'Resources' => 20 + ], + 'Other Stuff' => [ + 'Adoptables' => 21, + 'Auctions' => 22, + 'Contests' => 23, + 'Current Events' => 24, + 'Desktops' => 25, + 'Stockart' => 26, + 'Screenshots' => 27, + 'Scraps' => 28, + 'Wallpaper' => 29, + 'YCH / Sale' => 30, + 'Other' => 31 + ] + ], + 'defaultValue' => 1 + ], + 'atype' => [ + 'name' => 'Type', + 'type' => 'list', + 'values' => [ + 'General Things' => [ + 'All' => 1, + 'Abstract' => 2, + 'Animal related (non-anthro)' => 3, + 'Anime' => 4, + 'Comics' => 5, + 'Doodle' => 6, + 'Fanart' => 7, + 'Fantasy' => 8, + 'Human' => 9, + 'Portraits' => 10, + 'Scenery' => 11, + 'Still Life' => 12, + 'Tutorials' => 13, + 'Miscellaneous' => 14 + ], + 'Fetish / Furry specialty' => [ + 'Baby fur' => 101, + 'Bondage' => 102, + 'Digimon' => 103, + 'Fat Furs' => 104, + 'Fetish Other' => 105, + 'Fursuit' => 106, + 'Gore / Macabre Art' => 119, + 'Hyper' => 107, + 'Inflation' => 108, + 'Macro / Micro' => 109, + 'Muscle' => 110, + 'My Little Pony / Brony' => 111, + 'Paw' => 112, + 'Pokemon' => 113, + 'Pregnancy' => 114, + 'Sonic' => 115, + 'Transformation' => 116, + 'Vore' => 117, + 'Water Sports' => 118, + 'General Furry Art' => 100 + ], + 'Music' => [ + 'Techno' => 201, + 'Trance' => 202, + 'House' => 203, + '90s' => 204, + '80s' => 205, + '70s' => 206, + '60s' => 207, + 'Pre-60s' => 208, + 'Classical' => 209, + 'Game Music' => 210, + 'Rock' => 211, + 'Pop' => 212, + 'Rap' => 213, + 'Industrial' => 214, + 'Other Music' => 200 + ] + ], + 'defaultValue' => 1 + ], + 'species' => [ + 'name' => 'Species', + 'type' => 'list', + 'values' => [ + 'Unspecified / Any' => 1, + 'Amphibian' => [ + 'Frog' => 1001, + 'Newt' => 1002, + 'Salamander' => 1003, + 'Amphibian (Other)' => 1000 + ], + 'Aquatic' => [ + 'Cephalopod' => 2001, + 'Dolphin' => 2002, + 'Fish' => 2005, + 'Porpoise' => 2004, + 'Seal' => 6068, + 'Shark' => 2006, + 'Whale' => 2003, + 'Aquatic (Other)' => 2000 + ], + 'Avian' => [ + 'Corvid' => 3001, + 'Crow' => 3002, + 'Duck' => 3003, + 'Eagle' => 3004, + 'Falcon' => 3005, + 'Goose' => 3006, + 'Gryphon' => 3007, + 'Hawk' => 3008, + 'Owl' => 3009, + 'Phoenix' => 3010, + 'Swan' => 3011, + 'Avian (Other)' => 3000 + ], + 'Bears & Ursines' => [ + 'Bear' => 6002 + ], + 'Camelids' => [ + 'Camel' => 6074, + 'Llama' => 6036 + ], + 'Canines & Lupines' => [ + 'Coyote' => 6008, + 'Doberman' => 6009, + 'Dog' => 6010, + 'Dingo' => 6011, + 'German Shepherd' => 6012, + 'Jackal' => 6013, + 'Husky' => 6014, + 'Wolf' => 6016, + 'Canine (Other)' => 6017 + ], + 'Cervines' => [ + 'Cervine (Other)' => 6018 + ], + 'Cows & Bovines' => [ + 'Antelope' => 6004, + 'Cows' => 6003, + 'Gazelle' => 6005, + 'Goat' => 6006, + 'Bovines (General)' => 6007 + ], + 'Dragons' => [ + 'Eastern Dragon' => 4001, + 'Hydra' => 4002, + 'Serpent' => 4003, + 'Western Dragon' => 4004, + 'Wyvern' => 4005, + 'Dragon (Other)' => 4000 + ], + 'Equestrians' => [ + 'Donkey' => 6019, + 'Horse' => 6034, + 'Pony' => 6073, + 'Zebra' => 6071 + ], + 'Exotic & Mythicals' => [ + 'Argonian' => 5002, + 'Chakat' => 5003, + 'Chocobo' => 5004, + 'Citra' => 5005, + 'Crux' => 5006, + 'Daemon' => 5007, + 'Digimon' => 5008, + 'Dracat' => 5009, + 'Draenei' => 5010, + 'Elf' => 5011, + 'Gargoyle' => 5012, + 'Iksar' => 5013, + 'Kaiju/Monster' => 5015, + 'Langurhali' => 5014, + 'Moogle' => 5017, + 'Naga' => 5016, + 'Orc' => 5018, + 'Pokemon' => 5019, + 'Satyr' => 5020, + 'Sergal' => 5021, + 'Tanuki' => 5022, + 'Unicorn' => 5023, + 'Xenomorph' => 5024, + 'Alien (Other)' => 5001, + 'Exotic (Other)' => 5000 + ], + 'Felines' => [ + 'Domestic Cat' => 6020, + 'Cheetah' => 6021, + 'Cougar' => 6022, + 'Jaguar' => 6023, + 'Leopard' => 6024, + 'Lion' => 6025, + 'Lynx' => 6026, + 'Ocelot' => 6027, + 'Panther' => 6028, + 'Tiger' => 6029, + 'Feline (Other)' => 6030 + ], + 'Insects' => [ + 'Arachnid' => 8000, + 'Mantid' => 8004, + 'Scorpion' => 8005, + 'Insect (Other)' => 8003 + ], + 'Mammals (Other)' => [ + 'Bat' => 6001, + 'Giraffe' => 6031, + 'Hedgehog' => 6032, + 'Hippopotamus' => 6033, + 'Hyena' => 6035, + 'Panda' => 6052, + 'Pig/Swine' => 6053, + 'Rabbit/Hare' => 6059, + 'Raccoon' => 6060, + 'Red Panda' => 6062, + 'Meerkat' => 6043, + 'Mongoose' => 6044, + 'Rhinoceros' => 6063, + 'Mammals (Other)' => 6000 + ], + 'Marsupials' => [ + 'Opossum' => 6037, + 'Kangaroo' => 6038, + 'Koala' => 6039, + 'Quoll' => 6040, + 'Wallaby' => 6041, + 'Marsupial (Other)' => 6042 + ], + 'Mustelids' => [ + 'Badger' => 6045, + 'Ferret' => 6046, + 'Mink' => 6048, + 'Otter' => 6047, + 'Skunk' => 6069, + 'Weasel' => 6049, + 'Mustelid (Other)' => 6051 + ], + 'Primates' => [ + 'Gorilla' => 6054, + 'Human' => 6055, + 'Lemur' => 6056, + 'Monkey' => 6057, + 'Primate (Other)' => 6058 + ], + 'Reptillian' => [ + 'Alligator & Crocodile' => 7001, + 'Gecko' => 7003, + 'Iguana' => 7004, + 'Lizard' => 7005, + 'Snakes & Serpents' => 7006, + 'Turtle' => 7007, + 'Reptilian (Other)' => 7000 + ], + 'Rodents' => [ + 'Beaver' => 6064, + 'Mouse' => 6065, + 'Rat' => 6061, + 'Squirrel' => 6070, + 'Rodent (Other)' => 6067 + ], + 'Vulpines' => [ + 'Fennec' => 6072, + 'Fox' => 6075, + 'Vulpine (Other)' => 6015 + ], + 'Other' => [ + 'Dinosaur' => 8001, + 'Wolverine' => 6050 + ] + ], + 'defaultValue' => 1 + ], + 'gender' => [ + 'name' => 'Gender', + 'type' => 'list', + 'values' => [ + 'Any' => 0, + 'Male' => 2, + 'Female' => 3, + 'Herm' => 4, + 'Transgender' => 5, + 'Multiple characters' => 6, + 'Other / Not Specified' => 7 + ], + 'defaultValue' => 0 + ], + 'rating_general' => [ + 'name' => 'General', + 'type' => 'checkbox', + 'defaultValue' => 'checked' + ], + 'rating_mature' => [ + 'name' => 'Mature', + 'type' => 'checkbox', + ], + 'rating_adult' => [ + 'name' => 'Adult', + 'type' => 'checkbox', + ], + 'limit-browse' => [ + 'name' => 'Limit', + 'type' => 'number', + 'required' => true, + 'defaultValue' => 10, + 'title' => 'Limit number of submissions to return. -1 for unlimited.' + ], + 'full' => [ + 'name' => 'Full view', + 'title' => 'Include description, tags, date and larger image in article. Uses more bandwidth.', + 'type' => 'checkbox', + 'defaultValue' => 'checked' + ], + 'cache' => [ + 'name' => 'Cache submission pages', + 'title' => 'Reduces requests to FA when Full view is enabled. Changes to submission details may be delayed.', + 'type' => 'checkbox', + 'defaultValue' => 'checked' + ] + + ], + 'Journals' => [ + 'username-journals' => [ + 'name' => 'Username', + 'required' => true, + 'exampleValue' => 'dhw', + 'title' => 'Lowercase username as seen in URLs' + ], + 'limit' => [ + 'name' => 'Limit', + 'type' => 'number', + 'defaultValue' => -1, + 'title' => 'Limit number of journals to return. -1 for unlimited.' + ] + + ], + 'Single Journal' => [ + 'journal-id' => [ + 'name' => 'Journal ID', + 'required' => true, + 'exampleValue' => '10008853', + 'type' => 'number', + 'title' => 'Number seen in journal URL' + ] + ], + 'Gallery' => [ + 'username-gallery' => [ + 'name' => 'Username', + 'required' => true, + 'exampleValue' => 'dhw', + 'title' => 'Lowercase username as seen in URLs' + ], + 'limit' => [ + 'name' => 'Limit', + 'type' => 'number', + 'required' => true, + 'defaultValue' => 10, + 'title' => 'Limit number of submissions to return. -1 for unlimited.' + ], + 'full' => [ + 'name' => 'Full view', + 'title' => 'Include description, tags, date and larger image in article. Uses more bandwidth.', + 'type' => 'checkbox', + 'defaultValue' => 'checked' + ], + 'cache' => [ + 'name' => 'Cache submission pages', + 'title' => 'Reduces requests to FA when Full view is enabled. Changes to submission details may be delayed.', + 'type' => 'checkbox', + 'defaultValue' => 'checked' + ] + ], + 'Scraps' => [ + 'username-scraps' => [ + 'name' => 'Username', + 'required' => true, + 'exampleValue' => 'dhw', + 'title' => 'Lowercase username as seen in URLs' + ], + 'limit' => [ + 'name' => 'Limit', + 'type' => 'number', + 'required' => true, + 'defaultValue' => 10, + 'title' => 'Limit number of submissions to return. -1 for unlimited.' + ], + 'full' => [ + 'name' => 'Full view', + 'title' => 'Include description, tags, date and larger image in article. Uses more bandwidth.', + 'type' => 'checkbox', + 'defaultValue' => 'checked' + ], + 'cache' => [ + 'name' => 'Cache submission pages', + 'title' => 'Reduces requests to FA when Full view is enabled. Changes to submission details may be delayed.', + 'type' => 'checkbox', + 'defaultValue' => 'checked' + ] + ], + 'Favorites' => [ + 'username-favorites' => [ + 'name' => 'Username', + 'required' => true, + 'exampleValue' => 'dhw', + 'title' => 'Lowercase username as seen in URLs' + ], + 'limit' => [ + 'name' => 'Limit', + 'type' => 'number', + 'required' => true, + 'defaultValue' => 10, + 'title' => 'Limit number of submissions to return. -1 for unlimited.' + ], + 'full' => [ + 'name' => 'Full view', + 'title' => 'Include description, tags, date and larger image in article. Uses more bandwidth.', + 'type' => 'checkbox', + 'defaultValue' => 'checked' + ], + 'cache' => [ + 'name' => 'Cache submission pages', + 'title' => 'Reduces requests to FA when Full view is enabled. Changes to submission details may be delayed.', + 'type' => 'checkbox', + 'defaultValue' => 'checked' + ] + ], + 'Gallery Folder' => [ + 'username-folder' => [ + 'name' => 'Username', + 'required' => true, + 'exampleValue' => 'kopk', + 'title' => 'Lowercase username as seen in URLs' + ], + 'folder-id' => [ + 'name' => 'Folder ID', + 'required' => true, + 'exampleValue' => '1031990', + 'type' => 'number', + 'title' => 'Number seen in folder URL' + ], + 'limit' => [ + 'name' => 'Limit', + 'type' => 'number', + 'required' => true, + 'defaultValue' => 10, + 'title' => 'Limit number of submissions to return. -1 for unlimited.' + ], + 'full' => [ + 'name' => 'Full view', + 'title' => 'Include description, tags, date and larger image in article. Uses more bandwidth.', + 'type' => 'checkbox', + 'defaultValue' => 'checked' + ], + 'cache' => [ + 'name' => 'Cache submission pages', + 'title' => 'Reduces requests to FA when Full view is enabled. Changes to submission details may be delayed.', + 'type' => 'checkbox', + 'defaultValue' => 'checked' + ] + ] + ]; + + /* + * This was aquired by creating a new user on FA then + * extracting the cookie from the browsers dev console. + */ + const FA_AUTH_COOKIE = 'b=4ce65691-b50f-4742-a990-bf28d6de16ee; a=ca6e4566-9d81-4263-9444-653b142e35f8'; + + public function detectParameters($url) + { + $params = []; + + // Single journal + $regex = '/^(https?:\/\/)?(www\.)?furaffinity.net\/journal\/(\d+)/'; + if (preg_match($regex, $url, $matches) > 0) { + $params['journal-id'] = urldecode($matches[3]); + return $params; + } + + // Journals + $regex = '/^(https?:\/\/)?(www\.)?furaffinity.net\/journals\/([^\/&?\n]+)/'; + if (preg_match($regex, $url, $matches) > 0) { + $params['username-journals'] = urldecode($matches[3]); + return $params; + } + + // Gallery folder + $regex = '/^(https?:\/\/)?(www\.)?furaffinity.net\/gallery\/([^\/&?\n]+)\/folder\/(\d+)/'; + if (preg_match($regex, $url, $matches) > 0) { + $params['username-folder'] = urldecode($matches[3]); + $params['folder-id'] = urldecode($matches[4]); + $params['full'] = 'on'; + return $params; + } + + // Gallery (must be after gallery folder) + $regex = '/^(https?:\/\/)?(www\.)?furaffinity.net\/(gallery|scraps|favorites)\/([^\/&?\n]+)/'; + if (preg_match($regex, $url, $matches) > 0) { + $params['username-' . $matches[3]] = urldecode($matches[4]); + $params['full'] = 'on'; + return $params; + } + + return null; + } + + public function getName() + { + switch ($this->queriedContext) { + case 'Search': + return 'Search For ' + . $this->getInput('q'); + case 'Browse': + return 'Browse'; + case 'Journals': + return $this->getInput('username-journals'); + case 'Single Journal': + return 'Journal ' + . $this->getInput('journal-id'); + case 'Gallery': + return $this->getInput('username-gallery'); + case 'Scraps': + return $this->getInput('username-scraps'); + case 'Favorites': + return $this->getInput('username-favorites'); + case 'Gallery Folder': + return $this->getInput('username-folder') + . '\'s Folder ' + . $this->getInput('folder-id'); + default: + return parent::getName(); + } + } + + public function getDescription() + { + switch ($this->queriedContext) { + case 'Search': + return 'FurAffinity Search For ' + . $this->getInput('q'); + case 'Browse': + return 'FurAffinity Browse'; + case 'Journals': + return 'FurAffinity Journals By ' + . $this->getInput('username-journals'); + case 'Single Journal': + return 'FurAffinity Journal ' + . $this->getInput('journal-id'); + case 'Gallery': + return 'FurAffinity Gallery By ' + . $this->getInput('username-gallery'); + case 'Scraps': + return 'FurAffinity Scraps By ' + . $this->getInput('username-scraps'); + case 'Favorites': + return 'FurAffinity Favorites By ' + . $this->getInput('username-favorites'); + case 'Gallery Folder': + return 'FurAffinity Gallery Folder ' + . $this->getInput('folder-id') + . ' By ' + . $this->getInput('username-folder'); + default: + return parent::getDescription(); + } + } + + public function getURI() + { + switch ($this->queriedContext) { + case 'Search': + return self::URI + . '/search'; + case 'Browse': + return self::URI + . '/browse'; + case 'Journals': + return self::URI + . '/journals/' + . $this->getInput('username-journals'); + case 'Single Journal': + return self::URI + . '/journal/' + . $this->getInput('journal-id'); + case 'Gallery': + return self::URI + . '/gallery/' + . $this->getInput('username-gallery'); + case 'Scraps': + return self::URI + . '/scraps/' + . $this->getInput('username-scraps'); + case 'Favorites': + return self::URI + . '/favorites/' + . $this->getInput('username-favorites'); + case 'Gallery Folder': + return self::URI + . '/gallery/' + . $this->getInput('username-folder') + . '/folder/' + . $this->getInput('folder-id'); + default: + return parent::getURI(); + } + } + + public function collectData() + { + switch ($this->queriedContext) { + case 'Search': + $data = [ + 'q' => $this->getInput('q'), + 'perpage' => 72, + 'rating-general' => ($this->getInput('rating-general') === true ? 'on' : 0), + 'rating-mature' => ($this->getInput('rating-mature') === true ? 'on' : 0), + 'rating-adult' => ($this->getInput('rating-adult') === true ? 'on' : 0), + 'range' => $this->getInput('range'), + 'type-art' => ($this->getInput('type-art') === true ? 'on' : 0), + 'type-flash' => ($this->getInput('type-flash') === true ? 'on' : 0), + 'type-photo' => ($this->getInput('type-photo') === true ? 'on' : 0), + 'type-music' => ($this->getInput('type-music') === true ? 'on' : 0), + 'type-story' => ($this->getInput('type-story') === true ? 'on' : 0), + 'type-poetry' => ($this->getInput('type-poetry') === true ? 'on' : 0), + 'mode' => $this->getInput('mode') + ]; + $html = $this->postFASimpleHTMLDOM($data); + $limit = (is_int($this->getInput('limit')) ? $this->getInput('limit') : 10); + $this->itemsFromSubmissionList($html, $limit); + break; + case 'Browse': + $data = [ + 'cat' => $this->getInput('cat'), + 'atype' => $this->getInput('atype'), + 'species' => $this->getInput('species'), + 'gender' => $this->getInput('gender'), + 'perpage' => 72, + 'rating_general' => ($this->getInput('rating_general') === true ? 'on' : 0), + 'rating_mature' => ($this->getInput('rating_mature') === true ? 'on' : 0), + 'rating_adult' => ($this->getInput('rating_adult') === true ? 'on' : 0) + ]; + $html = $this->postFASimpleHTMLDOM($data); + $limit = (is_int($this->getInput('limit-browse')) ? $this->getInput('limit-browse') : 10); + $this->itemsFromSubmissionList($html, $limit); + break; + case 'Journals': + $html = $this->getFASimpleHTMLDOM($this->getURI()); + $limit = (is_int($this->getInput('limit')) ? $this->getInput('limit') : -1); + $this->itemsFromJournalList($html, $limit); + break; + case 'Single Journal': + $html = $this->getFASimpleHTMLDOM($this->getURI()); + $this->itemsFromJournal($html); + break; + case 'Gallery': + case 'Scraps': + case 'Favorites': + case 'Gallery Folder': + $html = $this->getFASimpleHTMLDOM($this->getURI()); + $limit = (is_int($this->getInput('limit')) ? $this->getInput('limit') : 10); + $this->itemsFromSubmissionList($html, $limit); + break; + } + } + + private function postFASimpleHTMLDOM($data) + { + $opts = [ + CURLOPT_CUSTOMREQUEST => 'POST', + CURLOPT_POSTFIELDS => http_build_query($data) + ]; + $header = [ + 'Host: ' . parse_url(self::URI, PHP_URL_HOST), + 'Content-Type: application/x-www-form-urlencoded', + 'Cookie: ' . self::FA_AUTH_COOKIE + ]; + + $html = getSimpleHTMLDOM($this->getURI(), $header, $opts); + $html = defaultLinkTo($html, $this->getURI()); + + return $html; + } + + private function getFASimpleHTMLDOM($url, $cache = false) + { + $header = [ + 'Cookie: ' . self::FA_AUTH_COOKIE + ]; + + if ($cache) { + $html = getSimpleHTMLDOMCached($url, 86400, $header); // 24 hours + } else { + $html = getSimpleHTMLDOM($url, $header); + } + + $html = defaultLinkTo($html, $url); + + return $html; + } + + private function itemsFromJournalList($html, $limit) + { + foreach ($html->find('table[id^=jid:]') as $journal) { + # allows limit = -1 to mean 'unlimited' + if ($limit-- === 0) { + break; + } + + $item = []; + + $this->setReferrerPolicy($journal); + + $item['uri'] = $journal->find('a', 0)->href; + $item['title'] = html_entity_decode($journal->find('a', 0)->plaintext); + $item['author'] = $this->getInput('username-journals'); + $item['timestamp'] = strtotime( + $journal->find('span.popup_date', 0)->plaintext + ); + $item['content'] = $journal + ->find('.alt1 table div.no_overflow', 0) + ->innertext; + + $this->items[] = $item; + } + } + + private function itemsFromJournal($html) + { + $this->setReferrerPolicy($html); + $item = []; + + $item['uri'] = $this->getURI(); + + $title = $html->find('.journal-title-box .no_overflow', 0)->plaintext; + $title = html_entity_decode($title); + $title = trim($title, " \t\n\r\0\x0B" . chr(0xC2) . chr(0xA0)); + $item['title'] = $title; + + $item['author'] = $html->find('.journal-title-box a', 0)->plaintext; + $item['timestamp'] = strtotime( + $html->find('.journal-title-box span.popup_date', 0)->plaintext + ); + $item['content'] = $html->find('.journal-body', 0)->innertext; + + $this->items[] = $item; + } + + private function itemsFromSubmissionList($html, $limit) + { + $cache = ($this->getInput('cache') === true); + + foreach ($html->find('section.gallery figure') as $figure) { + # allows limit = -1 to mean 'unlimited' + if ($limit-- === 0) { + break; + } + + $item = []; + + $submissionURL = $figure->find('b u a', 0)->href; + $imgURL = 'https:' . $figure->find('b u a img', 0)->src; + + $item['uri'] = $submissionURL; + $item['title'] = html_entity_decode( + $figure->find('figcaption p a[href*=/view/]', 0)->title + ); + $item['author'] = $figure->find('figcaption p a[href*=/user/]', 0)->title; + + if ($this->getInput('full') === true) { + $submissionHTML = $this->getFASimpleHTMLDOM($submissionURL, $cache); + + $stats = $submissionHTML->find('.stats-container', 0); + $item['timestamp'] = strtotime($stats->find('.popup_date', 0)->title); + $item['enclosures'] = [ + $submissionHTML->find('.actions a[href^=https://d.facdn]', 0)->href + ]; + foreach ($stats->find('#keywords a') as $keyword) { + $item['categories'][] = $keyword->plaintext; + } + + $previewSrc = $submissionHTML->find('#submissionImg', 0) + ->{'data-preview-src'}; + if ($previewSrc) { + $imgURL = 'https:' . $previewSrc; + } + + $description = $submissionHTML + ->find('.maintable .maintable tr td.alt1', -1); + $this->setReferrerPolicy($description); + $description = $description->innertext; + + $item['content'] = <<<EOD <a href="$submissionURL"> <img src="{$imgURL}" referrerpolicy="no-referrer" /> </a> @@ -905,27 +927,28 @@ class FurAffinityBridge extends BridgeAbstract { {$description} </p> EOD; - } else { - $item['content'] = <<<EOD + } else { + $item['content'] = <<<EOD <a href="$submissionURL"> <img src="$imgURL" referrerpolicy="no-referrer" /> </a> EOD; - } - - $this->items[] = $item; - } - } - - private function setReferrerPolicy(&$html) { - foreach($html->find('img') as $img) { - /* - * Note: Without the no-referrer policy their CDN sometimes denies requests. - * We can't control this for enclosures sadly. - * At least tt-rss adds the referrerpolicy on its own. - * Alternatively we could not use https for images, but that's not ideal. - */ - $img->referrerpolicy = 'no-referrer'; - } - } + } + + $this->items[] = $item; + } + } + + private function setReferrerPolicy(&$html) + { + foreach ($html->find('img') as $img) { + /* + * Note: Without the no-referrer policy their CDN sometimes denies requests. + * We can't control this for enclosures sadly. + * At least tt-rss adds the referrerpolicy on its own. + * Alternatively we could not use https for images, but that's not ideal. + */ + $img->referrerpolicy = 'no-referrer'; + } + } } |