Removed Twitter and Nitter support. It's over folks.

This commit is contained in:
flash 2023-07-06 22:53:02 +00:00
parent 4cff688057
commit d9094f0d05
9 changed files with 154 additions and 445 deletions

@ -1 +1 @@
Subproject commit bce5ba77a268ecd6338d0e3520e41ff4c40cbeda
Subproject commit 1cd1695429b5a313ac357f3f3faba365e425f504

View file

@ -10,7 +10,6 @@ $ctx->registerLookup(new \Uiharu\Lookup\EEPROMLookup('eeprom', 'eeprom.flashii.n
if(UIH_DEBUG)
$ctx->registerLookup(new \Uiharu\Lookup\EEPROMLookup('devrom', 'eeprom.edgii.net', ['i.edgii.net']));
$ctx->registerLookup(new \Uiharu\Lookup\TwitterLookup);
$ctx->registerLookup(new \Uiharu\Lookup\YouTubeLookup);
$ctx->registerLookup(new \Uiharu\Lookup\NicoNicoLookup);

View file

@ -13,9 +13,6 @@ use Uiharu\MediaTypeExts;
use Uiharu\UihContext;
use Uiharu\Url;
use Uiharu\Lookup\EEPROMLookupResult;
use Uiharu\Lookup\TwitterLookupResult;
use Uiharu\Lookup\TwitterLookupTweetResult;
use Uiharu\Lookup\TwitterLookupUserResult;
use Uiharu\Lookup\YouTubeLookupResult;
use Uiharu\Lookup\NicoNicoLookupResult;
use Index\MediaType;
@ -188,17 +185,6 @@ final class v1_0 implements \Uiharu\IApi {
if($result->hasPreviewImage())
$resp->image = $result->getPreviewImage();
if($result instanceof TwitterLookupResult) {
if($result instanceof TwitterLookupTweetResult)
$resp->tweet_id = $result->getTwitterTweetId();
if($result instanceof TwitterLookupUserResult)
$resp->twitter_user_name = $result->getTwitterUserName();
if(UIH_DEBUG)
$resp->dbg_twitter_info = $result->getTwitterResult();
}
if($result instanceof YouTubeLookupResult) {
$resp->youtube_video_id = $result->getYouTubeVideoId();

View file

@ -13,298 +13,298 @@ final class Colour {
switch($input) {
// CSS Level 1
case @"black":
case "black":
return self::BLACK;
case @"silver":
case "silver":
return self::SILVER;
case @"gray":
case @"grey": // CSS Level 3
case "gray":
case "grey": // CSS Level 3
return self::GREY;
case @"white":
case "white":
return self::WHITE;
case @"maroon":
case "maroon":
return self::MAROON;
case @"red":
case "red":
return self::RED;
case @"purple":
case "purple":
return self::PURPLE;
case @"fuchsia":
case @"magenta": // CSS Level 3
case "fuchsia":
case "magenta": // CSS Level 3
return self::MAGENTA;
case @"green":
case "green":
return self::GREEN;
case @"lime":
case "lime":
return self::LIME;
case @"olive":
case "olive":
return self::OLIVE;
case @"yellow":
case "yellow":
return self::YELLOW;
case @"navy":
case "navy":
return self::NAVY;
case @"blue":
case "blue":
return self::BLUE;
case @"teal":
case "teal":
return self::TEAL;
case @"aqua":
case @"cyan": // CSS Level 3
case "aqua":
case "cyan": // CSS Level 3
return self::CYAN;
// CSS Level 2
case @"orange":
case "orange":
return self::ORANGE;
// CSS Level 3
case @"aliceblue":
case "aliceblue":
return self::ALICEBLUE;
case @"antiquewhite":
case "antiquewhite":
return self::ANTIQUEWHITE;
case @"aquamarine":
case "aquamarine":
return self::AQUAMARINE;
case @"azure":
case "azure":
return self::AZURE;
case @"beige":
case "beige":
return self::BEIGE;
case @"bisque":
case "bisque":
return self::BISQUE;
case @"blanchedalmond":
case "blanchedalmond":
return self::BLANCHEDALMOND;
case @"blueviolet":
case "blueviolet":
return self::BLUEVIOLET;
case @"brown":
case "brown":
return self::BROWN;
case @"burlywood":
case "burlywood":
return self::BURLYWOOD;
case @"cadetblue":
case "cadetblue":
return self::CADETBLUE;
case @"chartreuse":
case "chartreuse":
return self::CHARTREUSE;
case @"chocolate":
case "chocolate":
return self::CHOCOLATE;
case @"coral":
case "coral":
return self::CORAL;
case @"cornflowerblue":
case "cornflowerblue":
return self::CORNFLOWERBLUE;
case @"cornsilk":
case "cornsilk":
return self::CORNSILK;
case @"crimson":
case "crimson":
return self::CRIMSON;
case @"darkblue":
case "darkblue":
return self::DARKBLUE;
case @"darkcyan":
case "darkcyan":
return self::DARKCYAN;
case @"darkgoldenrod":
case "darkgoldenrod":
return self::DARKGOLDENROD;
case @"darkgrey":
case @"darkgray":
case "darkgrey":
case "darkgray":
return self::DARKGREY;
case @"darkgreen":
case "darkgreen":
return self::DARKGREEN;
case @"darkkhaki":
case "darkkhaki":
return self::DARKKHAKI;
case @"darkmagenta":
case "darkmagenta":
return self::DARKMAGENTA;
case @"darkolivegreen":
case "darkolivegreen":
return self::DARKOLIVEGREEN;
case @"darkorange":
case "darkorange":
return self::DARKORANGE;
case @"darkorchid":
case "darkorchid":
return self::DARKORCHID;
case @"darkred":
case "darkred":
return self::DARKRED;
case @"darksalmon":
case "darksalmon":
return self::DARKSALMON;
case @"darkseagreen":
case "darkseagreen":
return self::DARKSEAGREEN;
case @"darkslateblue":
case "darkslateblue":
return self::DARKSLATEBLUE;
case @"darkslategrey":
case @"darkslategray":
case "darkslategrey":
case "darkslategray":
return self::DARKSLATEGREY;
case @"darkturquoise":
case "darkturquoise":
return self::DARKTURQUOISE;
case @"darkviolet":
case "darkviolet":
return self::DARKVIOLET;
case @"deeppink":
case "deeppink":
return self::DEEPPINK;
case @"deepskyblue":
case "deepskyblue":
return self::DEEPSKYBLUE;
case @"dimgray":
case @"dimgrey":
case "dimgray":
case "dimgrey":
return self::DIMGREY;
case @"dodgerblue":
case "dodgerblue":
return DodgerBluself::DODGERBLUE;
case @"firebrick":
case "firebrick":
return self::FIREBRICK;
case @"floralwhite":
case "floralwhite":
return self::FLORALWHITE;
case @"forestgreen":
case "forestgreen":
return self::FORESTGREEN;
case @"gainsboro":
case "gainsboro":
return self::GAINSBORO;
case @"ghostwhite":
case "ghostwhite":
return self::GHOSTWHITE;
case @"gold":
case "gold":
return self::GOLD;
case @"goldenrod":
case "goldenrod":
return self::GOLDENROD;
case @"greenyellow":
case "greenyellow":
return self::GREENYELLOW;
case @"honeydew":
case "honeydew":
return self::HONEYDEW;
case @"hotpink":
case "hotpink":
return self::HOTPINK;
case @"indianred":
case "indianred":
return self::INDIANRED;
case @"indigo":
case "indigo":
return self::INDIGO;
case @"ivory":
case "ivory":
return self::IVORY;
case @"khaki":
case "khaki":
return self::KHAKI;
case @"lavender":
case "lavender":
return self::LAVENDER;
case @"lavenderblush":
case "lavenderblush":
return self::LAVENDERBLUSH;
case @"lawngreen":
case "lawngreen":
return self::LAWNGREEN;
case @"lemonchiffon":
case "lemonchiffon":
return self::LEMONCHIFFON;
case @"lightblue":
case "lightblue":
return self::LIGHTBLUE;
case @"lightcoral":
case "lightcoral":
return self::LIGHTCORAL;
case @"lightcyan":
case "lightcyan":
return self::LIGHTCYAN;
case @"lightgoldenrodyellow":
case "lightgoldenrodyellow":
return self::LIGHTGOLDENRODYELLOW;
case @"lightgray":
case @"lightgrey":
case "lightgray":
case "lightgrey":
return self::LIGHTGREY;
case @"lightgreen":
case "lightgreen":
return self::LIGHTGREEN;
case @"lightpink":
case "lightpink":
return self::LIGHTPINK;
case @"lightsalmon":
case "lightsalmon":
return self::LIGHTSALMON;
case @"lightseagreen":
case "lightseagreen":
return self::LIGHTSEAGREEN;
case @"lightskyblue":
case "lightskyblue":
return self::LIGHTSEAGREEN;
case @"lightslategray":
case @"lightslategrey":
case "lightslategray":
case "lightslategrey":
return self::LIGHTSLATEGREY;
case @"lightsteelblue":
case "lightsteelblue":
return self::LIGHTSTEELBLUE;
case @"lightyellow":
case "lightyellow":
return self::LIGHTYELLOW;
case @"limegreen":
case "limegreen":
return self::LIMEGREEN;
case @"linen":
case "linen":
return self::LINEN;
case @"mediumaquamarine":
case "mediumaquamarine":
return self::MEDIUMAQUAMARINE;
case @"mediumblue":
case "mediumblue":
return self::MEDIUMBLUE;
case @"mediumorchid":
case "mediumorchid":
return self::MEDIUMORCHID;
case @"mediumpurple":
case "mediumpurple":
return self::MEDIUMPURPLE;
case @"mediumseagreen":
case "mediumseagreen":
return self::MEDIUMSEAGREEN;
case @"mediumslateblue":
case "mediumslateblue":
return self::MEDIUMSLATEBLUE;
case @"mediumspringgreen":
case "mediumspringgreen":
return self::MEDIUMSPRINGGREEN;
case @"mediumturquoise":
case "mediumturquoise":
return self::MEDIUMTURQUOISE;
case @"mediumvioletred":
case "mediumvioletred":
return self::MEDIUMVIOLETRED;
case @"midnightblue":
case "midnightblue":
return self::MIDNIGHTBLUE;
case @"mintcream":
case "mintcream":
return self::MINTCREAM;
case @"mistyrose":
case "mistyrose":
return self::MISTYROSE;
case @"moccasin":
case "moccasin":
return self::MOCCASIN;
case @"navajowhite":
case "navajowhite":
return self::NAVAJOWHITE;
case @"oldlace":
case "oldlace":
return self::OLDLACE;
case @"olivedrab":
case "olivedrab":
return self::OLIVEDRAB;
case @"orangered":
case "orangered":
return self::ORANGERED;
case @"orchid":
case "orchid":
return self::ORCHID;
case @"palegoldenrod":
case "palegoldenrod":
return self::PALEGOLDENROD;
case @"palegreen":
case "palegreen":
return self::PALEGREEN;
case @"paleturquoise":
case "paleturquoise":
return self::PALETURQUOISE;
case @"palevioletred":
case "palevioletred":
return self::PALEVIOLETRED;
case @"papayawhip":
case "papayawhip":
return self::PAPAYAWHIP;
case @"peachpuff":
case "peachpuff":
return self::PEACHPUFF;
case @"peru":
case "peru":
return self::PERU;
case @"pink":
case "pink":
return self::PINK;
case @"plum":
case "plum":
return self::PLUM;
case @"powderblue":
case "powderblue":
return self::POWDERBLUE;
case @"rosybrown":
case "rosybrown":
return self::ROSYBROWN;
case @"royalblue":
case "royalblue":
return self::ROYALBLUE;
case @"saddlebrown":
case "saddlebrown":
return self::SADDLEBROWN;
case @"salmon":
case "salmon":
return self::SALMON;
case @"sandybrown":
case "sandybrown":
return self::SANDYBROWN;
case @"seagreen":
case "seagreen":
return self::SEAGREEN;
case @"seashell":
case "seashell":
return self::SEASHELL;
case @"sienna":
case "sienna":
return self::SIENNA;
case @"skyblue":
case "skyblue":
return self::SKYBLUE;
case @"slateblue":
case "slateblue":
return self::SLATEBLUE;
case @"slategray":
case @"slategrey":
case "slategray":
case "slategrey":
return self::SLATEGREY;
case @"snow":
case "snow":
return self::SNOW;
case @"springgreen":
case "springgreen":
return self::SPRINGGREEN;
case @"steelblue":
case "steelblue":
return self::STEELBLUE;
case @"tan":
case "tan":
return self::TAN;
case @"thistle":
case "thistle":
return self::THISTLE;
case @"tomato":
case "tomato":
return self::TOMATO;
case @"turquoise":
case "turquoise":
return self::TURQUOISE;
case @"violet":
case "violet":
return self::VIOLET;
case @"wheat":
case "wheat":
return self::WHEAT;
case @"whitesmoke":
case "whitesmoke":
return self::WHITESMOKE;
case @"yellowgreen":
case "yellowgreen":
return self::YELLOWGREEN;
// CSS Level 4
case @"rebeccapurple":
case "rebeccapurple":
return self::REBECCAPURPLE;
}

View file

@ -1,135 +0,0 @@
<?php
namespace Uiharu\Lookup;
use stdClass;
use DOMDocument;
use DOMXpath;
use RuntimeException;
use Uiharu\Config;
use Uiharu\Url;
use Index\MediaType;
use Masterminds\HTML5;
final class TwitterLookup implements \Uiharu\ILookup {
private const TWITTER_DOMAINS = [
'twitter.com', 'www.twitter.com',
'm.twitter.com', 'mobile.twitter.com',
'nitter.net', 'www.nitter.net',
];
public function match(Url $url): bool {
if(!$url->isWeb() || !in_array(strtolower($url->getHost()), self::TWITTER_DOMAINS))
return false;
return preg_match('#^/@?(?:[A-Za-z0-9_]{1,20})/status(?:es)?/([0-9]+)/?$#', $url->getPath())
|| preg_match('#^/@?([A-Za-z0-9_]{1,20})/?$#', $url->getPath());
}
private function getString(string $path): string {
$curl = curl_init(Config::get('Nitter', 'endpoint') . $path);
curl_setopt_array($curl, [
CURLOPT_AUTOREFERER => false,
CURLOPT_CERTINFO => false,
CURLOPT_FAILONERROR => false,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TCP_FASTOPEN => true,
CURLOPT_CONNECTTIMEOUT => 2,
CURLOPT_PROTOCOLS => CURLPROTO_HTTPS,
CURLOPT_TIMEOUT => 5,
CURLOPT_USERAGENT => 'Uiharu/' . UIH_VERSION,
]);
$resp = curl_exec($curl);
curl_close($curl);
return $resp;
}
private function getDocument(string $path): DOMDocument {
$string = $this->getString($path);
if(empty($string))
throw new RuntimeException('Failed to download Nitter page.');
return (new HTML5)->loadHTML($string);
}
private static function convertNitterMediaURL(string $path): string {
if($path === '')
return $path;
[,,$url] = explode('/', $path);
$url = rawurldecode($url);
if(!str_starts_with($url, 'pbs.twimg.com'))
$url = 'pbs.twimg.com/' . str_replace('_bigger', '', $url);
return 'https://' . $url;
}
private function lookupUser(string $userName): ?object {
$document = $this->getDocument("/{$userName}");
$xpath = new DOMXpath($document);
$out = new stdClass;
$userNameElems = $xpath->query('//*[@class="profile-card-username"]');
$out->userName = $userNameElems->length < 1 ? '' : trim($userNameElems[0]->textContent);
$profileNameElems = $xpath->query('//*[@class="profile-card-fullname"]');
$out->profileName = $profileNameElems->length < 1 ? '' : trim($profileNameElems[0]->textContent);
$profileBioElems = $xpath->query('//*[@class="profile-bio"]/*');
$out->profileBio = $profileBioElems->length < 1 ? '' : trim($profileBioElems[0]->textContent);
$profilePictureElems = $xpath->query('//*[@class="profile-card-avatar"]');
$out->profilePicture = $profilePictureElems->length < 1 ? '' : $profilePictureElems[0]->getAttribute('href');
$out->profilePicture = self::convertNitterMediaURL($out->profilePicture);
return $out;
}
private function lookupTweet(string $tweetId): ?object {
$document = $this->getDocument("/i/status/{$tweetId}");
$xpath = new DOMXpath($document);
$out = new stdClass;
$tweetDateElems = $xpath->query('//*[@class="tweet-date"]/*');
$out->tweetId = $tweetDateElems->length < 1 ? '' : trim($tweetDateElems[0]->getAttribute('href'));
if($out->tweetId !== '') {
[,,,$out->tweetId] = explode('/', $out->tweetId);
[$out->tweetId] = explode('#', $out->tweetId);
}
$tweetTextElems = $xpath->query('//*[@class="tweet-content media-body"]');
$out->tweetText = $tweetTextElems->length < 1 ? '' : trim($tweetTextElems[0]->textContent);
$profileNameElems = $xpath->query('//*[@class="fullname"]');
$out->profileName = $profileNameElems->length < 1 ? '' : trim($profileNameElems[0]->textContent);
$profilePictureElems = $xpath->query('//*[@class="tweet-avatar"]/*');
$out->profilePicture = $profilePictureElems->length < 1 ? '' : $profilePictureElems[0]->getAttribute('src');
$out->profilePicture = self::convertNitterMediaURL($out->profilePicture);
return $out;
}
public function lookup(Url $url): TwitterLookupResult {
if(preg_match('#^/@?(?:[A-Za-z0-9_]{1,20})/status(?:es)?/([0-9]+)/?$#', $url->getPath(), $matches)) {
$tweetId = strval($matches[1] ?? '0');
$tweetInfo = $this->lookupTweet($tweetId);
if($tweetInfo === null)
throw new RuntimeException('Tweet lookup failed.');
return new TwitterLookupTweetResult($url, $tweetInfo);
}
if(preg_match('#^/@?([A-Za-z0-9_]{1,20})/?$#', $url->getPath(), $matches)) {
$userName = strval($matches[1] ?? '');
$userInfo = $this->lookupUser($userName);
if($userInfo === null)
throw new RuntimeException('Twitter user lookup failed.');
return new TwitterLookupUserResult($url, $userInfo);
}
throw new RuntimeException('Unknown Twitter URL format.');
}
}

View file

@ -1,51 +0,0 @@
<?php
namespace Uiharu\Lookup;
use RuntimeException;
use Uiharu\Url;
use Index\MediaType;
abstract class TwitterLookupResult implements \Uiharu\ILookupResult {
private Url $url;
public function __construct(Url $url) {
$this->url = $url;
}
public function getUrl(): Url {
return $this->url;
}
public abstract function getObjectType(): string;
public function hasMediaType(): bool {
return false;
}
public function getMediaType(): MediaType {
throw new RuntimeException('Unsupported');
}
public function hasColour(): bool {
return true;
}
public function getColour(): int {
return 0x1DA1F2;
}
public abstract function hasTitle(): bool;
public abstract function getTitle(): string;
public function hasSiteName(): bool {
return true;
}
public function getSiteName(): string {
return 'Twitter';
}
public abstract function hasDescription(): bool;
public abstract function getDescription(): string;
public abstract function hasPreviewImage(): bool;
public abstract function getPreviewImage(): string;
public abstract function getTwitterResult(): object;
}

View file

@ -1,46 +0,0 @@
<?php
namespace Uiharu\Lookup;
use Uiharu\Url;
class TwitterLookupTweetResult extends TwitterLookupResult {
private object $tweetInfo;
public function __construct(Url $url, object $tweetInfo) {
parent::__construct($url);
$this->tweetInfo = $tweetInfo;
}
public function getObjectType(): string {
return 'twitter:tweet';
}
public function getTwitterTweetId(): string {
return $this->tweetInfo->tweetId;
}
public function hasTitle(): bool {
return $this->tweetInfo->profileName !== '';
}
public function getTitle(): string {
return $this->tweetInfo->profileName;
}
public function hasDescription(): bool {
return $this->tweetInfo->tweetText !== '';
}
public function getDescription(): string {
return $this->tweetInfo->tweetText;
}
public function hasPreviewImage(): bool {
return $this->tweetInfo->profilePicture !== '';
}
public function getPreviewImage(): string {
return $this->tweetInfo->profilePicture;
}
public function getTwitterResult(): object {
return $this->tweetInfo;
}
}

View file

@ -1,46 +0,0 @@
<?php
namespace Uiharu\Lookup;
use Uiharu\Url;
class TwitterLookupUserResult extends TwitterLookupResult {
private object $userInfo;
public function __construct(Url $url, object $userInfo) {
parent::__construct($url);
$this->userInfo = $userInfo;
}
public function getObjectType(): string {
return 'twitter:user';
}
public function getTwitterUserName(): string {
return $this->userInfo->userName;
}
public function hasTitle(): bool {
return $this->userInfo->profileName !== '';
}
public function getTitle(): string {
return $this->userInfo->profileName;
}
public function hasDescription(): bool {
return $this->userInfo->profileBio !== '';
}
public function getDescription(): string {
return $this->userInfo->profileBio;
}
public function hasPreviewImage(): bool {
return $this->userInfo->profilePicture !== '';
}
public function getPreviewImage(): string {
return $this->userInfo->profilePicture;
}
public function getTwitterResult(): object {
return $this->userInfo;
}
}

View file

@ -32,7 +32,7 @@ final class WebLookup implements \Uiharu\ILookup {
CURLOPT_REDIR_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS,
CURLOPT_TIMEOUT => 5,
CURLOPT_DEFAULT_PROTOCOL => 'https',
CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible) Uiharu/' . UIH_VERSION,
CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; FlashiiBot/2.5; Uiharu/' . UIH_VERSION . '; +http://fii.moe/uiharu)',
CURLOPT_HTTPHEADER => [
'Accept: text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8',
],
@ -200,11 +200,13 @@ final class WebLookup implements \Uiharu\ILookup {
break;
case 'theme-color':
$siteInfo->colour = $valueAttr;
if(empty($siteInfo->colour))
$siteInfo->colour = $valueAttr;
break;
case 'og:type':
$siteInfo->type = 'website:' . $valueAttr;
if(empty($siteInfo->type))
$siteInfo->type = 'website:' . $valueAttr;
break;
}
}