Füge Brawls Bridges hinzu
This commit is contained in:
45
BSICertBridge.php
Normal file
45
BSICertBridge.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
class BSICertBridge extends BridgeAbstract
|
||||
{
|
||||
const MAINTAINER = 'Brawl';
|
||||
const NAME = 'BSI Bürger-CERT-Sicherheitshinweise';
|
||||
const URI = 'https://www.bsi.bund.de/';
|
||||
const CACHE_TIMEOUT = 21600; // 21600 = 6h
|
||||
const DESCRIPTION = 'Sicherheitshinweise des Bürger-CERT vom Bundesministerium für Internetsicherheit';
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
return self::URI . "SiteGlobals/Frontend/Images/favicons/android-chrome-256x256.png?__blob=normal&v=3";
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
// Retrieve webpage
|
||||
$pageUrl = self::URI . 'DE/Service-Navi/Abonnements/Newsletter/Buerger-CERT-Abos/Buerger-CERT-Sicherheitshinweise/buerger-cert-sicherheitshinweise_node.html';
|
||||
$html = getSimpleHTMLDOM($pageUrl)
|
||||
or returnServerError('Could not request webpage: ' . $pageUrl);
|
||||
|
||||
foreach ($html->find('tbody > tr') as $element) {
|
||||
|
||||
if (count($this->items) >= 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
$title_cell = $element->find('td', 1);
|
||||
$article_title = trim($title_cell->find('a', 0)->plaintext);
|
||||
$article_uri = self::URI . trim($title_cell->find('a', 0)->href);
|
||||
$article_content = trim(substr($title_cell->plaintext, strlen($article_title)));
|
||||
$article_timestamp = strtotime($element->find('td', 2)->plaintext);
|
||||
|
||||
// Store article in items array
|
||||
if (!empty($article_title)) {
|
||||
$item = array();
|
||||
$item['uri'] = $article_uri;
|
||||
$item['title'] = $article_title;
|
||||
$item['content'] = $article_content;
|
||||
$item['timestamp'] = $article_timestamp;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
72
CUIIBridge.php
Normal file
72
CUIIBridge.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
class CUIIBridge extends BridgeAbstract
|
||||
{
|
||||
|
||||
const MAINTAINER = 'Brawl, ChatGPT';
|
||||
const NAME = 'CUII-Sperrungen';
|
||||
const URI = 'https://cuii.info/empfehlungen/';
|
||||
const CACHE_TIMEOUT = 21600; // 21600 = 6h
|
||||
const DESCRIPTION = 'Zeigt die neuesten Sperrungen der CUII (Clearingstelle Urheberrecht im Internet)';
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
return 'https://cuii.info/typo3conf/ext/cuii_template/Resources/Public/Images/favicon.png';
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$html = getSimpleHTMLDOM(self::URI) or returnServerError('Could not request cuii.info.');
|
||||
|
||||
foreach ($html->find('.card') as $card) {
|
||||
$item = array();
|
||||
|
||||
$title = $card->find('span[itemprop=text]', 0);
|
||||
if ($title) {
|
||||
$item['title'] = $title->plaintext;
|
||||
|
||||
$pattern = '/vom ([0-9]{1,2}\\. [a-zA-ZäöüÄÖÜß]+ [0-9]{4})/';
|
||||
preg_match($pattern, $item['title'], $matches);
|
||||
if (!empty($matches)) {
|
||||
$date_parts = explode(' ', $matches[1]);
|
||||
$date_parts[1] = $this->translateMonth($date_parts[1]);
|
||||
$english_date = implode(' ', $date_parts);
|
||||
$date = DateTime::createFromFormat('d. F Y', $english_date);
|
||||
if ($date !== false) {
|
||||
$date->setTime(0, 0);
|
||||
$item['timestamp'] = $date->getTimestamp();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$pdf_link = $card->find('p.pdf a', 0);
|
||||
if ($pdf_link) {
|
||||
$item['uri'] = 'https://cuii.info' . $pdf_link->href;
|
||||
$item['uid'] = $item['uri'];
|
||||
$item['content'] = '<a href="' . $item['uri'] . '">Empfehlung zum Download als PDF</a>';
|
||||
} else {
|
||||
$item['content'] = 'Empfehlung zum Download als PDF';
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function translateMonth($month)
|
||||
{
|
||||
$months = array(
|
||||
'Januar' => 'January',
|
||||
'Februar' => 'February',
|
||||
'März' => 'March',
|
||||
'April' => 'April',
|
||||
'Mai' => 'May',
|
||||
'Juni' => 'June',
|
||||
'Juli' => 'July',
|
||||
'August' => 'August',
|
||||
'September' => 'September',
|
||||
'Oktober' => 'October',
|
||||
'November' => 'November',
|
||||
'Dezember' => 'December'
|
||||
);
|
||||
return $months[$month] ?? $month;
|
||||
}
|
||||
}
|
50
CemuReleasesBridge.php
Normal file
50
CemuReleasesBridge.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
class CemuReleasesBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'Brawl';
|
||||
const NAME = 'Cemu Releases';
|
||||
const URI = 'https://cemu.info/';
|
||||
const CACHE_TIMEOUT = 21600; // 21600 = 6h
|
||||
const DESCRIPTION = 'Returns the latest Cemu releases.';
|
||||
|
||||
public function collectData() {
|
||||
// Retrieve webpage
|
||||
$pageUrl = self::URI . 'changelog.html';
|
||||
$html = getSimpleHTMLDOM($pageUrl)
|
||||
or returnServerError('Could not request webpage: ' . $pageUrl);
|
||||
|
||||
// Process articles
|
||||
foreach($html->find('div[class=col-sm-12 well]') as $element) {
|
||||
|
||||
if(count($this->items) >= 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
$title_array = explode('|', $element->find('h2.changelog', 0)->innertext);
|
||||
$title_array_len = count($title_array);
|
||||
$article_title = trim(strip_tags($title_array[0]));
|
||||
|
||||
$article_content = '';
|
||||
if ($title_array_len >= 3) {
|
||||
$article_content .= str_replace('<a href="', '<a href="' . self::URI,$title_array[2]);
|
||||
}
|
||||
if ($title_array_len >= 4) {
|
||||
$article_content .= ' | ' . $title_array[3];
|
||||
}
|
||||
$article_content .= $element->find('ul', 0);
|
||||
|
||||
$article_timestamp = strtotime(strip_tags($title_array[1]));
|
||||
|
||||
// Store article in items array
|
||||
if (!empty($article_title)) {
|
||||
$item = array();
|
||||
$item['uri'] = $pageUrl;
|
||||
$item['title'] = $article_title;
|
||||
$item['content'] = $article_content;
|
||||
$item['timestamp'] = $article_timestamp;
|
||||
$item['uid'] = $article_title;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
155
CosppiBridge.php
Normal file
155
CosppiBridge.php
Normal file
@ -0,0 +1,155 @@
|
||||
<?php
|
||||
class CosppiBridge extends BridgeAbstract
|
||||
{
|
||||
|
||||
const MAINTAINER = 'Brawl, GPT-4';
|
||||
const NAME = 'Cosppi';
|
||||
const URI = 'https://cosppi.net/';
|
||||
const CACHE_TIMEOUT = 10800; // 10800 = 3h
|
||||
const DESCRIPTION = 'Tweets from Cosplayers scraped by Cosppi';
|
||||
const PARAMETERS = [
|
||||
'global' => [
|
||||
'sort' => [
|
||||
'name' => 'Sort by',
|
||||
'type' => 'list',
|
||||
'required' => true,
|
||||
'values' => [
|
||||
'Latest' => 'new',
|
||||
'Popularity' => 'rank'
|
||||
]
|
||||
]
|
||||
],
|
||||
'By Search Query' => [
|
||||
'query' => [
|
||||
'name' => 'Query',
|
||||
'title' => 'See https://cosppi.net/tag-list',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => '雷電将軍',
|
||||
]
|
||||
],
|
||||
'By User' => [
|
||||
'user' => [
|
||||
'name' => 'Username',
|
||||
'title' => 'See https://cosppi.net/sort/all-rank',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => 'enako_cos',
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
private $query = "";
|
||||
|
||||
public function getName()
|
||||
{
|
||||
if (empty($this->queriedContext)) {
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
switch ($this->queriedContext) {
|
||||
case 'By Search Query':
|
||||
return parent::getName() . ': ' . $this->query;
|
||||
case 'By User':
|
||||
return parent::getName() . ' (' . $this->query . ')';
|
||||
default:
|
||||
returnServerError('Unimplemented Context (getName)');
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
if (empty($this->queriedContext)) {
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
switch ($this->queriedContext) {
|
||||
case 'By Search Query':
|
||||
return parent::getURI() . 'search-images?word=' . $this->query . '&sort=' . $this->getInput('sort');
|
||||
case 'By User':
|
||||
return parent::getURI() . 'user/' . $this->query . '?sort=' . $this->getInput('sort');
|
||||
default:
|
||||
returnServerError('Unimplemented Context (getURI)');
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
return 'https://cosppi.net/wp-content/uploads/2020/01/cropped-favicon-1-192x192.png';
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
// Retrieve webpage
|
||||
$this->query = $this->getInput('query') ?: $this->getInput('user');
|
||||
$pageUrl = $this->getURI();
|
||||
$html = getSimpleHTMLDOM($pageUrl)
|
||||
or returnServerError('Could not request webpage: ' . $pageUrl);
|
||||
|
||||
// Process articles
|
||||
foreach ($html->find('div.img_wrapper') as $element) {
|
||||
|
||||
if (count($this->items) >= 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
$name = "";
|
||||
$username = "";
|
||||
|
||||
switch ($this->queriedContext) {
|
||||
case 'By Search Query':
|
||||
$profile_area = $element->find('div.all_tweet_profile_wrap', 0);
|
||||
$name = trim(strip_tags($profile_area->find('div.all_tweet_profile_name', 0)->innertext));
|
||||
$username = trim(strip_tags($profile_area->find('div.all_tweet_profile_screenName', 0)->innertext));
|
||||
break;
|
||||
case 'By User':
|
||||
$profile_area = $html->find('div.prof_right_wrap', 0);
|
||||
$name = trim(strip_tags($profile_area->find('div.prof_name', 0)->innertext));
|
||||
$username = trim(strip_tags($profile_area->find('div.prof_scname', 0)->innertext));
|
||||
break;
|
||||
default:
|
||||
returnServerError('Unimplemented Context (collectData)');
|
||||
}
|
||||
|
||||
$media_link_element = $element->find('.img_a', 0);
|
||||
$media_link = $media_link_element->getAttribute('data-link') ?: $media_link_element->href;
|
||||
$media_type = $media_link_element->getAttribute('data-link') ? 'video' : 'image';
|
||||
|
||||
$date_str = trim(strip_tags($element->find('div.img_footer span', 0)->innertext));
|
||||
$title = trim(strip_tags($element->find('div.tweet_text', 0)->innertext));
|
||||
|
||||
$tweet_link_element = $element->find('div.img_footer a', 0);
|
||||
$tweet_link = $tweet_link_element ? $tweet_link_element->href : $media_link;
|
||||
|
||||
// Constructing the title
|
||||
$article_title = $name . " (" . $username . ")";
|
||||
if (!empty($title)) {
|
||||
$article_title .= ": " . $title;
|
||||
}
|
||||
|
||||
// Convert date_str to timestamp
|
||||
$timestamp = strtotime($date_str);
|
||||
|
||||
// Store article in items array
|
||||
if (!empty($name)) {
|
||||
$item = array();
|
||||
$item['uid'] = $tweet_link;
|
||||
$item['uri'] = $tweet_link;
|
||||
$item['title'] = $article_title;
|
||||
$item['content'] = $title . '<br>';
|
||||
if ($media_type === 'image') {
|
||||
$item['content'] .= '<img src="' . $media_link . ':orig">';
|
||||
} else {
|
||||
$item['content'] .= '<video controls><source src="' . $media_link . '" type="video/mp4"></video>';
|
||||
}
|
||||
// $item['enclosures'] = array($image_link);
|
||||
$item['timestamp'] = $timestamp;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
115
DMAXBridge.php
Normal file
115
DMAXBridge.php
Normal file
@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
class DMAXBridge extends BridgeAbstract
|
||||
{
|
||||
|
||||
const MAINTAINER = 'Brawl';
|
||||
const NAME = 'DMAX';
|
||||
const URI = 'https://www.dmax.de/';
|
||||
const CACHE_TIMEOUT = 7200; // 7200 = 2h
|
||||
const DESCRIPTION = 'Returns the newest episode of a DMAX show.';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'show' => array(
|
||||
'name' => 'Show ID (e.g. "6023" for Steel Buddies, check website source code)',
|
||||
'type' => 'number',
|
||||
'required' => true
|
||||
),
|
||||
)
|
||||
);
|
||||
const TOKEN_URI = 'https://eu1-prod.disco-api.com/token?realm=dmaxde';
|
||||
const DISCO_URI = 'https://eu1-prod.disco-api.com/content/videos//?include=primaryChannel,primaryChannel.images,show,show.images,genres,tags,images,contentPackages&sort=-seasonNumber,-episodeNumber&filter[show.id]=%d&filter[videoType]=EPISODE&page[number]=1&page[size]=100';
|
||||
|
||||
private $showName = '';
|
||||
private $pageUrl = self::URI . 'sendungen/';
|
||||
|
||||
public function getName()
|
||||
{
|
||||
if (!empty($this->showName)) {
|
||||
return $this->showName;
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
return self::URI . 'apple-touch-icon.png';
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
return $this->pageUrl;
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
// Retrieve and check user input
|
||||
$show = $this->getInput('show');
|
||||
if (empty($show))
|
||||
returnClientError('Invalid show: ' . $show);
|
||||
|
||||
// Get Token
|
||||
$tokenUrl = getSimpleHTMLDOM(self::TOKEN_URI)
|
||||
or returnServerError('Could not request DMAX token.');
|
||||
|
||||
$token_json = json_decode($tokenUrl, true);
|
||||
$token = $token_json['data']['attributes']['token'];
|
||||
if (empty($token))
|
||||
returnServerError('Could not get DMAX token.');
|
||||
|
||||
// Retrieve discovery URI
|
||||
$pageUrl = sprintf(self::DISCO_URI, $show);
|
||||
$html = getSimpleHTMLDOM($pageUrl, array('Authorization: Bearer ' . $token))
|
||||
or returnServerError('Could not request DMAX discovery URI: ' . $pageUrl);
|
||||
$json = json_decode($html, true);
|
||||
|
||||
// Get show name
|
||||
foreach ($json["included"] as $incl_element) {
|
||||
if ($incl_element["type"] == "show") {
|
||||
$this->showName = $incl_element['attributes']['name'];
|
||||
$this->pageUrl = self::URI . 'sendungen/' . $incl_element['attributes']['alternateId'];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($this->showName))
|
||||
returnClientError('Show not found.');
|
||||
|
||||
// Process articles
|
||||
foreach ($json['data'] as $element) {
|
||||
|
||||
if (count($this->items) >= 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
$episodeTitle = trim($element['attributes']['name']);
|
||||
if (array_key_exists('episodeNumber', $element['attributes']) // Both season + episode no. given
|
||||
&& array_key_exists('seasonNumber', $element['attributes'])) {
|
||||
$article_title = sprintf($this->showName . ' S%02dE%02d: ' . $episodeTitle,
|
||||
$element['attributes']['seasonNumber'],
|
||||
$element['attributes']['episodeNumber']);
|
||||
} elseif (array_key_exists('episodeNumber', $element['attributes']) // Only season no. given
|
||||
&& !array_key_exists('seasonNumber', $element['attributes'])) {
|
||||
$article_title = sprintf($this->showName . ' E%02d: ' . $episodeTitle,
|
||||
$element['attributes']['episodeNumber']);
|
||||
} else { // Nothing given
|
||||
$article_title = $this->showName . ' - ' . $episodeTitle;
|
||||
}
|
||||
$article_content = trim($element['attributes']['description']);
|
||||
|
||||
$article_time = $element['attributes']['airDate'];
|
||||
|
||||
// Store article in items array
|
||||
if (!empty($article_title)) {
|
||||
$item = array();
|
||||
$item['uri'] = $this->pageUrl . '/videos';
|
||||
$item['title'] = $article_title;
|
||||
$item['enclosures'] = array();
|
||||
$item['content'] = $article_content;
|
||||
$item['timestamp'] = $article_time;
|
||||
$item['uid'] = $article_title;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
121
GalleryEpicBridge.php
Normal file
121
GalleryEpicBridge.php
Normal file
@ -0,0 +1,121 @@
|
||||
<?php
|
||||
class GalleryEpicBridge extends BridgeAbstract
|
||||
{
|
||||
const MAINTAINER = 'Brawl, Gemini';
|
||||
const NAME = 'GalleryEpic Bridge';
|
||||
const URI = 'https://galleryepic.com/';
|
||||
const DESCRIPTION = 'Returns the latest albums for a cosplayer on GalleryEpic.';
|
||||
const PARAMETERS = [
|
||||
[
|
||||
'cosplayer_id' => [
|
||||
'name' => 'Cosplayer ID',
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'title' => 'Enter the Cosplayer ID (e.g., 95 from https://galleryepic.com/en/coser/95/1)',
|
||||
'exampleValue' => 95
|
||||
]
|
||||
]
|
||||
];
|
||||
const CACHE_TIMEOUT = 21600; // 6 hours
|
||||
|
||||
private $feedName = null;
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$cosplayerId = $this->getInput('cosplayer_id');
|
||||
if (!$cosplayerId) {
|
||||
returnServerError('Cosplayer ID is required.');
|
||||
}
|
||||
|
||||
// Set a default feed name before fetching content
|
||||
$this->feedName = static::NAME . ' for Cosplayer ID ' . $cosplayerId;
|
||||
|
||||
$url = self::URI . 'en/coser/' . $cosplayerId . '/1';
|
||||
$html = getSimpleHTMLDOM($url);
|
||||
|
||||
if (!$html) {
|
||||
returnServerError('Could not request_uri: ' . $url);
|
||||
}
|
||||
|
||||
// Extract cosplayer name for a more specific feed title
|
||||
$cosplayerNameElement = $html->find('h4.scroll-m-20.text-xl.font-semibold.tracking-tight', 0);
|
||||
if ($cosplayerNameElement) {
|
||||
$cosplayerName = trim($cosplayerNameElement->plaintext);
|
||||
$this->feedName = $cosplayerName . ' - GalleryEpic Albums';
|
||||
} else {
|
||||
// Fallback if name couldn't be extracted, keep the default with ID
|
||||
$this->feedName = 'GalleryEpic Albums for Cosplayer ID ' . $cosplayerId;
|
||||
}
|
||||
|
||||
$albums = $html->find('div.space-y-3.relative');
|
||||
|
||||
foreach ($albums as $albumElement) {
|
||||
$item = [];
|
||||
|
||||
$linkElement = $albumElement->find('a.space-y-3', 0);
|
||||
if ($linkElement) {
|
||||
$item['uri'] = self::URI . ltrim($linkElement->href, '/');
|
||||
}
|
||||
|
||||
$titleElement = $albumElement->find('h3.font-medium', 0);
|
||||
if ($titleElement) {
|
||||
$item['title'] = $titleElement->plaintext;
|
||||
}
|
||||
|
||||
$seriesElement = $albumElement->find('p.text-xs.text-muted-foreground', 0);
|
||||
$seriesText = $seriesElement ? $seriesElement->plaintext : '';
|
||||
|
||||
$imageElement = $albumElement->find('img[variant="cover"]', 0);
|
||||
$imageUrl = $imageElement ? $imageElement->src : '';
|
||||
$imagePCountElement = $albumElement->find('p.absolute.bottom-2.right-2', 0);
|
||||
$imagePCount = $imagePCountElement ? str_replace('P', '', $imagePCountElement->plaintext) : '';
|
||||
|
||||
$item['content'] = '<p>';
|
||||
if ($seriesText) {
|
||||
$item['content'] .= 'Series: ' . $seriesText . '<br>';
|
||||
}
|
||||
if ($imagePCount) {
|
||||
$item['content'] .= 'Pictures: ' . $imagePCount . '<br>';
|
||||
}
|
||||
$item['content'] .= '</p>';
|
||||
if ($imageUrl) {
|
||||
$item['content'] .= '<a href="' . $item['uri'] . '"><img src="' . $imageUrl . '" /></a>';
|
||||
}
|
||||
|
||||
// Try to find a download link (optional)
|
||||
$downloadLinkElement = $albumElement->find('a[href*="/download/cosplay/"]', 0);
|
||||
if ($downloadLinkElement) {
|
||||
$item['content'] .= '<br><a href="' . self::URI . ltrim($downloadLinkElement->href, '/') . '">Download Album</a>';
|
||||
}
|
||||
|
||||
if (isset($item['uri']) && isset($item['title'])) {
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
return 'https://galleryepic.com/icons/icon-192x192.png';
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
if (!is_null($this->feedName)) {
|
||||
return $this->feedName;
|
||||
}
|
||||
// Fallback for before collectData runs or if no cosplayer_id input
|
||||
if (!is_null($this->getInput('cosplayer_id'))) {
|
||||
return static::NAME . ' for Cosplayer ID ' . $this->getInput('cosplayer_id');
|
||||
}
|
||||
return parent::getName(); // Returns static::NAME by default
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
if (!is_null($this->getInput('cosplayer_id'))) {
|
||||
return self::URI . 'en/coser/' . $this->getInput('cosplayer_id') . '/1';
|
||||
}
|
||||
return parent::getURI();
|
||||
}
|
||||
}
|
42
JapanTimesFeaturesBridge.php
Normal file
42
JapanTimesFeaturesBridge.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
class JapanTimesFeaturesBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'Brawl';
|
||||
const NAME = 'Deep Reads by The Japan Times';
|
||||
const URI = 'https://features.japantimes.co.jp/';
|
||||
const CACHE_TIMEOUT = 21600; // 21600 = 6h
|
||||
const DESCRIPTION = 'Deep Dives from the JT.';
|
||||
|
||||
public function collectData() {
|
||||
// Retrieve webpage
|
||||
$pageUrl = self::URI;
|
||||
$html = getSimpleHTMLDOM($pageUrl)
|
||||
or returnServerError('Could not request webpage: ' . $pageUrl);
|
||||
|
||||
// Process articles
|
||||
foreach($html->find('div.esg-media-cover-wrapper') as $element) {
|
||||
|
||||
if(count($this->items) >= 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
$article_title = trim(strip_tags($element->find('div.eg-jt-features-grid-skin-element-0', 0)->innertext));
|
||||
$article_uri = $element->find('a.eg-invisiblebutton', 0)->href;
|
||||
$article_thumbnail = $element->find('img', 0)->src;
|
||||
$article_content = '<img src="'. $article_thumbnail .'"><br>';
|
||||
$article_content .= trim(strip_tags($element->find('div.eg-jt-features-grid-skin-element-6', 0)->innertext));
|
||||
$article_timestamp = strtotime($element->find('div.eg-jt-features-grid-skin-element-24', 0)->innertext);
|
||||
|
||||
// Store article in items array
|
||||
if (!empty($article_title)) {
|
||||
$item = array();
|
||||
$item['uri'] = $article_uri;
|
||||
$item['title'] = $article_title;
|
||||
//$item['enclosures'] = array($article_thumbnail);
|
||||
$item['content'] = $article_content;
|
||||
$item['timestamp'] = $article_timestamp;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
92
SSBUNewsBridge.php
Normal file
92
SSBUNewsBridge.php
Normal file
@ -0,0 +1,92 @@
|
||||
<?php
|
||||
class SSBUNewsBridge extends BridgeAbstract
|
||||
{
|
||||
|
||||
const MAINTAINER = 'Brawl';
|
||||
const NAME = 'Super Smash Bros. Ultimate News';
|
||||
const URI = 'https://www-aaaba-lp1-hac.cdn.nintendo.net/';
|
||||
const CACHE_TIMEOUT = 43200; // 43200 = 12h
|
||||
const DESCRIPTION = 'Returns the latest Super Smash Bros. Ultimate news.';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'lang' => array(
|
||||
'name' => 'Language',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'English (US)' => 'en-US',
|
||||
'Chinese (Simplified)' => 'zh-CN',
|
||||
'Chinese (Traditional)' => 'zh-TW',
|
||||
'Dutch' => 'nl',
|
||||
'English (GB)' => 'en-GB',
|
||||
'French' => 'fr',
|
||||
'German' => 'de',
|
||||
'Italian' => 'it',
|
||||
'Japanese' => 'ja',
|
||||
'Korean' => 'ko',
|
||||
'Russian' => 'ru'
|
||||
),
|
||||
'defaultValue' => 'en-US'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
private $messagesString = '';
|
||||
private $pageUrl = 'https://www-aaaba-lp1-hac.cdn.nintendo.net/en-US/index.html';
|
||||
|
||||
public function getName()
|
||||
{
|
||||
if (!empty($this->messagesString)) {
|
||||
return $this->messagesString;
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
return 'https://www.smashbros.com/favicon.ico';
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
return $this->pageUrl;
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
// Retrieve webpage
|
||||
$lang = $this->getInput('lang');
|
||||
$pageUrlBase = self::URI . $lang . '/';
|
||||
$pageUrl = $pageUrlBase . 'index.html';
|
||||
$html = getSimpleHTMLDOM($pageUrl)
|
||||
or returnServerError('Could not request webpage: ' . $pageUrl);
|
||||
|
||||
$this->messagesString = $html->find('title', 0)->plaintext . ' ' . $html->find('div.shrink-label', 0)->plaintext;
|
||||
$this->pageUrl = $pageUrl;
|
||||
|
||||
// Process articles
|
||||
foreach ($html->find('li.article-item') as $element) {
|
||||
|
||||
if (count($this->items) >= 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
$article_title = trim($element->find('h2', 0)->plaintext);
|
||||
$article_uri = $pageUrlBase . $element->find('a', 0)->href;
|
||||
$article_thumbnail = $pageUrlBase . $element->find('img', 0)->{'data-lazy-src'};
|
||||
$article_content = '<a href="' . $article_uri . '"><img src="' . $article_thumbnail . '"></a>';
|
||||
$article_timestamp = $element->attr['data-show-new-badge-published-at'];
|
||||
|
||||
// Store article in items array
|
||||
if (!empty($article_title) && !empty($article_uri)) {
|
||||
$item = array();
|
||||
$item['uri'] = $article_uri;
|
||||
$item['title'] = $article_title;
|
||||
// $item['enclosures'] = array($article_thumbnail);
|
||||
$item['content'] = $article_content;
|
||||
$item['timestamp'] = $article_timestamp;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
47
WHODiseaseOutbreakBridge.php
Normal file
47
WHODiseaseOutbreakBridge.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
class WHODiseaseOutbreakBridge extends BridgeAbstract
|
||||
{
|
||||
|
||||
const MAINTAINER = 'Brawl';
|
||||
const NAME = 'WHO Disease Outbreak News';
|
||||
const URI = 'https://www.who.int/emergencies/disease-outbreak-news';
|
||||
const CACHE_TIMEOUT = 21600; // 21600 = 6h
|
||||
const DESCRIPTION = 'Latest WHO Disease Outbreak News (DONs), providing information on confirmed acute public health events or potential events of concern.';
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
// Retrieve webpage
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not request webpage: ' . self::URI);
|
||||
|
||||
// Process articles
|
||||
foreach ($html->find('div.sf-list-vertical a.sf-list-vertical__item') as $element) {
|
||||
|
||||
if (count($this->items) >= 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
$row = $element->find('.sf-list-vertical__title', 0);
|
||||
|
||||
$article_link = $element->href;
|
||||
$article_title = trim($row->find('.full-title', 0)->plaintext);
|
||||
|
||||
// Store article in items array
|
||||
if (!empty($article_title)) {
|
||||
$item = array();
|
||||
$item['uri'] = $article_link;
|
||||
$item['title'] = $article_title;
|
||||
$item['uid'] = $article_link;
|
||||
}
|
||||
|
||||
$timestamp = $row->find('span', 1)->plaintext;
|
||||
if (isset($timestamp) && !empty($timestamp)) {
|
||||
$timestamp = str_replace(' | ', '', $timestamp);
|
||||
$article_timestamp = strtotime($timestamp);
|
||||
$item['timestamp'] = $article_timestamp;
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
395
WeiboPicsBridge.php
Normal file
395
WeiboPicsBridge.php
Normal file
@ -0,0 +1,395 @@
|
||||
<?php
|
||||
class WeiboPicsBridge extends BridgeAbstract
|
||||
{
|
||||
const MAINTAINER = 'Akamaru, Brawl, Gemini 2.5 Pro';
|
||||
const NAME = 'Weibo User Pictures';
|
||||
const URI = 'https://weibo.com';
|
||||
const CACHE_TIMEOUT = 3600; // 1 hour for feed data
|
||||
const DESCRIPTION = 'Get the latest pictures from a Weibo user.';
|
||||
|
||||
const PARAMETERS = [[
|
||||
'uid' => [
|
||||
'name' => 'User ID',
|
||||
'required' => true,
|
||||
'exampleValue' => '2813217452', // <3
|
||||
'title' => 'The numeric user ID from Weibo'
|
||||
],
|
||||
]];
|
||||
|
||||
private const GUEST_COOKIE_CACHE_KEY_PREFIX = 'WeiboPicsBridge_Guest_'; // Prefix for cache key
|
||||
private const GUEST_COOKIE_TTL = 21600; // 6 hours for guest cookies
|
||||
|
||||
private $feedTitleName = '';
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
return 'https://weibo.com/favicon.ico';
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
if (empty($this->feedTitleName)) {
|
||||
return parent::getName();
|
||||
}
|
||||
return $this->feedTitleName;
|
||||
}
|
||||
|
||||
private function getGuestCookieCacheKey()
|
||||
{
|
||||
// Make cache key unique per bridge instance if needed, though guest cookies are general.
|
||||
// For simplicity, using a general key.
|
||||
return self::GUEST_COOKIE_CACHE_KEY_PREFIX . 'cookies';
|
||||
}
|
||||
|
||||
private function getGuestCookiesOrLogin()
|
||||
{
|
||||
$cacheKey = $this->getGuestCookieCacheKey();
|
||||
$cachedCookies = $this->loadCacheValue($cacheKey, self::GUEST_COOKIE_TTL);
|
||||
|
||||
if ($cachedCookies !== null && is_array($cachedCookies) && !empty($cachedCookies)) {
|
||||
$this->logger->debug('Using cached guest cookies.');
|
||||
return $cachedCookies;
|
||||
}
|
||||
$this->logger->debug('No valid cached guest cookies found. Attempting guest login.');
|
||||
|
||||
// --- Step 1: genvisitor ---
|
||||
$genvisitorUrl = 'https://passport.weibo.com/visitor/genvisitor';
|
||||
$fpData = [
|
||||
'os' => '1',
|
||||
'browser' => 'Gecko109,0,0,0',
|
||||
'fonts' => 'undefined',
|
||||
'screenInfo' => '1920*1080*24',
|
||||
'plugins' => ''
|
||||
];
|
||||
$postFields = http_build_query([
|
||||
'cb' => 'gen_callback',
|
||||
'fp' => json_encode($fpData)
|
||||
]);
|
||||
|
||||
$genvisitorHeaders = [
|
||||
'Referer: https://passport.weibo.com/visitor/visitor?entry=miniblog&page=request',
|
||||
'Content-Type: application/x-www-form-urlencoded'
|
||||
];
|
||||
$genvisitorCurlOptions = [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $postFields
|
||||
];
|
||||
|
||||
/** @var Response|string $genvisitorResponseObj */
|
||||
$genvisitorResponseObj = getContents($genvisitorUrl, $genvisitorHeaders, $genvisitorCurlOptions, true);
|
||||
|
||||
if (!$genvisitorResponseObj instanceof Response) {
|
||||
$this->logger->warning('genvisitor request failed to return a Response object.');
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
$genvisitorResponseBody = $genvisitorResponseObj->getBody();
|
||||
|
||||
if (empty($genvisitorResponseBody)) {
|
||||
$this->logger->warning('genvisitor response body is empty.');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!preg_match('/gen_callback\((.*)\);/s', $genvisitorResponseBody, $matches)) {
|
||||
$this->logger->warning('Could not parse genvisitor JSONP response: ' . substr($genvisitorResponseBody, 0, 200));
|
||||
return null;
|
||||
}
|
||||
$jsonData = json_decode($matches[1], true);
|
||||
|
||||
if (!isset($jsonData['data']['tid']) || empty($jsonData['data']['tid'])) {
|
||||
$this->logger->warning('TID not found in genvisitor response: ' . $matches[1]);
|
||||
return null;
|
||||
}
|
||||
|
||||
$tid = $jsonData['data']['tid'];
|
||||
$newTid = isset($jsonData['data']['new_tid']) && $jsonData['data']['new_tid'];
|
||||
$confidence = $jsonData['data']['confidence'] ?? 100;
|
||||
|
||||
$this->logger->debug('genvisitor successful. TID: ' . $tid);
|
||||
|
||||
// --- Step 2: visitor ---
|
||||
$wParam = $newTid ? '3' : '2';
|
||||
$cParam = sprintf('%03d', $confidence);
|
||||
$randParam = mt_rand() / mt_getrandmax();
|
||||
|
||||
$visitorUrl = sprintf(
|
||||
'https://passport.weibo.com/visitor/visitor?a=incarnate&t=%s&w=%s&c=%s&gc=&cb=cross_domain&from=weibo&_rand=%s',
|
||||
urlencode($tid),
|
||||
urlencode($wParam),
|
||||
urlencode($cParam),
|
||||
$randParam
|
||||
);
|
||||
|
||||
$visitorHeaders = [
|
||||
];
|
||||
// No specific cURL options needed for this GET request beyond headers
|
||||
|
||||
/** @var Response|string $visitorResponseObj */
|
||||
$visitorResponseObj = getContents($visitorUrl, $visitorHeaders, [], true);
|
||||
|
||||
if (!$visitorResponseObj instanceof Response) {
|
||||
$this->logger->warning('Visitor request failed to return a Response object.');
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($visitorResponseObj->getCode() >= 300) {
|
||||
$this->logger->warning('Visitor request failed with HTTP code: ' . $visitorResponseObj->getCode());
|
||||
return null;
|
||||
}
|
||||
$this->logger->debug('Visitor request successful.');
|
||||
|
||||
$cookiesToStore = [];
|
||||
// Extract Set-Cookie headers from visitor response
|
||||
$vistorHeaders = $visitorResponseObj->getHeaders();
|
||||
$visitorSetCookieHeaders = $vistorHeaders['set-cookie'] ?? [];
|
||||
if (is_array($visitorSetCookieHeaders)) {
|
||||
foreach ($visitorSetCookieHeaders as $headerValue) {
|
||||
if (preg_match('/^([^=]+=[^;]+)/i', $headerValue, $cookieMatch)) {
|
||||
$cookiesToStore[] = $cookieMatch[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also check cookies potentially set by genvisitor call if any were relevant
|
||||
$genvisitorHeaders = $genvisitorResponseObj->getHeaders();
|
||||
$genvisitorSetCookieHeaders = $genvisitorHeaders['set-cookie'] ?? [];
|
||||
if (is_array($genvisitorSetCookieHeaders)) {
|
||||
foreach ($genvisitorSetCookieHeaders as $headerValue) {
|
||||
if (preg_match('/^([^=]+=[^;]+)/i', $headerValue, $cookieMatch)) {
|
||||
// Avoid duplicates if same cookie name was set by both
|
||||
$isDuplicate = false;
|
||||
$newCookieName = explode('=', $cookieMatch[1])[0];
|
||||
foreach($cookiesToStore as $existingCookie) {
|
||||
if (explode('=', $existingCookie)[0] === $newCookieName) {
|
||||
$isDuplicate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$isDuplicate) {
|
||||
$cookiesToStore[] = $cookieMatch[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (empty($cookiesToStore)) {
|
||||
$this->logger->warning('No cookies were set by the visitor login flow.');
|
||||
return null;
|
||||
}
|
||||
|
||||
$this->saveCacheValue($cacheKey, $cookiesToStore, self::GUEST_COOKIE_TTL);
|
||||
$this->logger->info('Guest cookies obtained and cached: ' . implode('; ', $cookiesToStore));
|
||||
return $cookiesToStore;
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$uid = $this->getInput('uid');
|
||||
if (!is_numeric($uid)) {
|
||||
returnClientError('User ID must be numeric.');
|
||||
}
|
||||
|
||||
$guestCookiesArray = $this->getGuestCookiesOrLogin();
|
||||
if ($guestCookiesArray === null) {
|
||||
returnServerError('Failed to obtain Weibo guest cookies. The API might be inaccessible or login failed.');
|
||||
}
|
||||
|
||||
$commonRequestHeaders = [
|
||||
'X-Requested-With: XMLHttpRequest',
|
||||
'Referer: https://weibo.com/u/' . $uid,
|
||||
'Cookie: ' . implode('; ', $guestCookiesArray)
|
||||
];
|
||||
|
||||
$imageWallUrl = 'https://weibo.com/ajax/profile/getImageWall?uid=' . $uid . '&sinceid=0&has_album=true';
|
||||
|
||||
$maxAttempts = 3;
|
||||
$attempt = 0;
|
||||
$imageWallResponseObj = null; // Initialize
|
||||
|
||||
do {
|
||||
$attempt++;
|
||||
if ($attempt > 1) {
|
||||
$this->logger->debug("Retrying getImageWall for UID {$uid}, attempt {$attempt}/{$maxAttempts}.");
|
||||
// Optional: consider a small delay, e.g., sleep(1);
|
||||
}
|
||||
/** @var Response|string|false $currentAttemptResponseObj */
|
||||
$currentAttemptResponseObj = getContents($imageWallUrl, $commonRequestHeaders, [], true);
|
||||
|
||||
if ($currentAttemptResponseObj instanceof Response) {
|
||||
$httpCode = $currentAttemptResponseObj->getCode();
|
||||
if ($httpCode === 500) {
|
||||
$imageWallResponseObj = $currentAttemptResponseObj; // Store the 500 response for now
|
||||
if ($attempt < $maxAttempts) {
|
||||
$this->logger->info("Weibo getImageWall API for UID {$uid} returned HTTP 500 on attempt {$attempt}/{$maxAttempts}. Retrying...");
|
||||
continue; // Go to next attempt
|
||||
}
|
||||
// Max attempts reached for 500, loop will terminate, and this 500 response will be handled below
|
||||
$this->logger->warning("Weibo getImageWall API for UID {$uid} still returned HTTP 500 after {$maxAttempts} attempts.");
|
||||
break;
|
||||
}
|
||||
// Not a 500 error, so this is our final response from the loop
|
||||
$imageWallResponseObj = $currentAttemptResponseObj;
|
||||
break;
|
||||
} elseif ($currentAttemptResponseObj === false || $currentAttemptResponseObj === null) {
|
||||
// Critical getContents failure
|
||||
$imageWallResponseObj = $currentAttemptResponseObj; // Store false/null
|
||||
break;
|
||||
} else {
|
||||
// Unexpected response type from getContents
|
||||
$imageWallResponseObj = $currentAttemptResponseObj; // Store unexpected type
|
||||
break;
|
||||
}
|
||||
} while ($attempt < $maxAttempts);
|
||||
|
||||
$imageWallJson = null;
|
||||
// Default to empty, successful-like structure for imageWallData
|
||||
$imageWallData = ['ok' => 1, 'data' => ['list' => [], 'user' => []]];
|
||||
|
||||
if ($imageWallResponseObj instanceof Response) {
|
||||
$httpCode = $imageWallResponseObj->getCode();
|
||||
if ($httpCode === 500) { // This means all retries (if any) resulted in 500
|
||||
$this->logger->info('Weibo getImageWall API for UID ' . $uid . ' returned HTTP 500 (after all retries). Proceeding with empty data for image wall.');
|
||||
// $imageWallData is already set to an empty valid structure, $imageWallJson remains null.
|
||||
} elseif ($httpCode >= 300) { // Other HTTP errors (excluding 500 which is handled above)
|
||||
returnServerError('Weibo getImageWall API request failed for UID ' . $uid . '. HTTP Code: ' . $httpCode . '. Body: ' . substr($imageWallResponseObj->getBody(), 0, 200));
|
||||
} else { // Successful response (2xx)
|
||||
$imageWallJson = $imageWallResponseObj->getBody();
|
||||
$decodedData = json_decode($imageWallJson, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
returnServerError('Failed to decode JSON from image wall for UID ' . $uid . ': ' . json_last_error_msg() . ' Response: ' . substr($imageWallJson, 0, 500));
|
||||
}
|
||||
|
||||
if (!isset($decodedData['ok']) || $decodedData['ok'] != 1) {
|
||||
$msg = $decodedData['msg'] ?? 'Unknown error from Weibo getImageWall API.';
|
||||
if (stripos($msg, 'login') !== false || ($imageWallJson && stripos($imageWallJson, 'login.sina.com.cn') !== false)) {
|
||||
$this->logger->warning('Weibo API redirected to login for getImageWall, UID ' . $uid . '. Guest cookies might be invalid. Clearing cache.');
|
||||
$this->saveCacheValue($this->getGuestCookieCacheKey(), null, 0); // Invalidate cache
|
||||
returnServerError('Weibo getImageWall API requires login for UID ' . $uid . '. Guest session failed or expired. Message: ' . $msg);
|
||||
}
|
||||
returnServerError('Invalid response from Weibo getImageWall API (after 2xx) for UID ' . $uid . ': ' . $msg . ' Raw: ' . substr($imageWallJson, 0, 200));
|
||||
}
|
||||
$imageWallData = $decodedData; // Use the successfully decoded data
|
||||
}
|
||||
} elseif ($imageWallResponseObj === false || $imageWallResponseObj === null) {
|
||||
// getContents itself failed critically (e.g., cURL error, DNS issue)
|
||||
returnServerError('Could not request image wall (getContents failed critically) for UID ' . $uid . ': ' . $imageWallUrl);
|
||||
} else {
|
||||
// Should not happen if getContents with true as 4th param behaves as expected (Response or false/null)
|
||||
$this->logger->warning('Unexpected response type from getContents for imageWall, UID ' . $uid . ': ' . gettype($imageWallResponseObj));
|
||||
returnServerError('Unexpected response from getContents for imageWall API for UID ' . $uid);
|
||||
}
|
||||
|
||||
$postsToFetchDetails = [];
|
||||
if (isset($imageWallData['data']['list']) && is_array($imageWallData['data']['list'])) {
|
||||
$processedMids = [];
|
||||
foreach ($imageWallData['data']['list'] as $postSummary) {
|
||||
if (isset($postSummary['mid']) && !empty($postSummary['mid'])) {
|
||||
if (!in_array($postSummary['mid'], $processedMids)) {
|
||||
$processedMids[] = $postSummary['mid'];
|
||||
$postsToFetchDetails[] = $postSummary['mid'];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->logger->debug('Image wall list is not an array or not set, or empty. Response: ' . $imageWallJson);
|
||||
}
|
||||
|
||||
if (empty($postsToFetchDetails)) {
|
||||
$this->logger->debug('No posts found in image wall for UID: ' . $uid);
|
||||
}
|
||||
|
||||
$feedTitleName = 'User ' . $uid;
|
||||
if (isset($imageWallData['data']['user']['screen_name'])) {
|
||||
$feedTitleName = $imageWallData['data']['user']['screen_name'];
|
||||
} elseif (isset($imageWallData['data']['list'][0]['user']['screen_name'])) { // If list not empty
|
||||
$feedTitleName = $imageWallData['data']['list'][0]['user']['screen_name'];
|
||||
} else {
|
||||
$userInfoUrl = 'https://weibo.com/ajax/profile/info?uid=' . $uid;
|
||||
$userInfoJson = getContents($userInfoUrl, $commonRequestHeaders);
|
||||
if ($userInfoJson) {
|
||||
$userInfoData = json_decode($userInfoJson, true);
|
||||
if (isset($userInfoData['data']['user']['screen_name'])) {
|
||||
$feedTitleName = $userInfoData['data']['user']['screen_name'];
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->feedTitleName = $feedTitleName . ' - Weibo Pictures';
|
||||
|
||||
foreach (array_slice($postsToFetchDetails, 0, 15) as $mid) {
|
||||
$postDetailUrl = 'https://weibo.com/ajax/statuses/show?id=' . $mid;
|
||||
$postJson = getContents($postDetailUrl, $commonRequestHeaders);
|
||||
|
||||
if ($postJson === false || $postJson === null) {
|
||||
$this->logger->warning('Could not request post details for MID: ' . $mid);
|
||||
continue;
|
||||
}
|
||||
|
||||
$postData = json_decode($postJson, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
$this->logger->warning('Failed to decode JSON for post MID ' . $mid . ': ' . json_last_error_msg() . ' Raw: ' . substr($postJson, 0, 200));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isset($postData['ok']) || $postData['ok'] != 1) {
|
||||
$this->logger->warning('Post detail API error for MID ' . $mid . ': ' . ($postData['msg'] ?? 'Unknown error'));
|
||||
continue;
|
||||
}
|
||||
|
||||
$mblogid = $postData['mblogid'] ?? $mid;
|
||||
$postUser = $postData['user'] ?? null;
|
||||
$postUid = $postUser['idstr'] ?? $uid;
|
||||
$postLink = 'https://weibo.com/' . $postUid . '/' . $mblogid;
|
||||
|
||||
$titleText = $postData['text_raw'] ?? 'Weibo Post ' . $mid;
|
||||
$title = mb_strlen($titleText) > 70 ? mb_substr($titleText, 0, 70, 'UTF-8') . '...' : $titleText;
|
||||
if (empty(trim($title))) $title = 'Weibo Image Post ' . $mid;
|
||||
|
||||
$htmlContent = '<p>' . nl2br(htmlspecialchars($postData['text_raw'] ?? '')) . '</p>';
|
||||
$timestamp = isset($postData['created_at']) ? strtotime($postData['created_at']) : time();
|
||||
|
||||
$imageHtml = '';
|
||||
if (isset($postData['pic_ids']) && is_array($postData['pic_ids']) && isset($postData['pic_infos']) && is_array($postData['pic_infos'])) {
|
||||
foreach ($postData['pic_ids'] as $pic_id) {
|
||||
if (isset($postData['pic_infos'][$pic_id]['largest']['url'])) {
|
||||
$imageUrl = $postData['pic_infos'][$pic_id]['largest']['url'];
|
||||
if (strpos($imageUrl, 'http:') === 0) $imageUrl = 'https:' . substr($imageUrl, 5);
|
||||
$imageHtml .= '<figure><img src="' . htmlspecialchars($imageUrl) . '" /><figcaption><a href="' . htmlspecialchars($imageUrl) . '">' . htmlspecialchars($imageUrl) . '</a></figcaption></figure>';
|
||||
}
|
||||
}
|
||||
} elseif (isset($postData['page_info']['type'])) {
|
||||
$pageInfo = $postData['page_info'];
|
||||
$imgUrlKey = null;
|
||||
if (isset($pageInfo['page_pic']['url'])) $imgUrlKey = $pageInfo['page_pic']['url'];
|
||||
elseif (isset($pageInfo['media_info']['video_poster'])) $imgUrlKey = $pageInfo['media_info']['video_poster'];
|
||||
|
||||
if ($imgUrlKey) {
|
||||
$imageUrl = $imgUrlKey;
|
||||
if (strpos($imageUrl, 'http:') === 0) $imageUrl = 'https:' . substr($imageUrl, 5);
|
||||
$imageHtml .= '<figure><img src="' . htmlspecialchars($imageUrl) . '" /><figcaption>Cover Image: <a href="' . htmlspecialchars($imageUrl) . '">' . htmlspecialchars($imageUrl) . '</a></figcaption></figure>';
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($imageHtml)) {
|
||||
$this->logger->debug('Post MID ' . $mid . ' had no extractable images in detail view.');
|
||||
continue;
|
||||
}
|
||||
$htmlContent .= $imageHtml;
|
||||
|
||||
$item = [];
|
||||
$item['uri'] = $postLink;
|
||||
$item['title'] = $title;
|
||||
$item['content'] = $htmlContent;
|
||||
$item['timestamp'] = $timestamp;
|
||||
$item['uid'] = $mid;
|
||||
$item['author'] = $postUser['screen_name'] ?? $feedTitleName;
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if (count($this->items) >= 10) break;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user