TwitterBridge.php 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. <?php
  2. class TwitterBridge extends BridgeAbstract {
  3. const NAME = 'Twitter Bridge';
  4. const URI = 'https://twitter.com/';
  5. const CACHE_TIMEOUT = 300; // 5min
  6. const DESCRIPTION = 'returns tweets';
  7. const MAINTAINER = 'pmaziere';
  8. const PARAMETERS = array(
  9. 'global' => array(
  10. 'nopic' => array(
  11. 'name' => 'Hide profile pictures',
  12. 'type' => 'checkbox',
  13. 'title' => 'Activate to hide profile pictures in content'
  14. ),
  15. 'noimg' => array(
  16. 'name' => 'Hide images in tweets',
  17. 'type' => 'checkbox',
  18. 'title' => 'Activate to hide images in tweets'
  19. )
  20. ),
  21. 'By keyword or hashtag' => array(
  22. 'q' => array(
  23. 'name' => 'Keyword or #hashtag',
  24. 'required' => true,
  25. 'exampleValue' => 'rss-bridge, #rss-bridge',
  26. 'title' => 'Insert a keyword or hashtag'
  27. )
  28. ),
  29. 'By username' => array(
  30. 'u' => array(
  31. 'name' => 'username',
  32. 'required' => true,
  33. 'exampleValue' => 'sebsauvage',
  34. 'title' => 'Insert a user name'
  35. ),
  36. 'norep' => array(
  37. 'name' => 'Without replies',
  38. 'type' => 'checkbox',
  39. 'title' => 'Only return initial tweets'
  40. ),
  41. 'noretweet' => array(
  42. 'name' => 'Without retweets',
  43. 'required' => false,
  44. 'type' => 'checkbox',
  45. 'title' => 'Hide retweets'
  46. )
  47. )
  48. );
  49. public function getName(){
  50. switch($this->queriedContext) {
  51. case 'By keyword or hashtag':
  52. $specific = 'search ';
  53. $param = 'q';
  54. break;
  55. case 'By username':
  56. $specific = '@';
  57. $param = 'u';
  58. break;
  59. default: return parent::getName();
  60. }
  61. return 'Twitter ' . $specific . $this->getInput($param);
  62. }
  63. public function getURI(){
  64. switch($this->queriedContext) {
  65. case 'By keyword or hashtag':
  66. return self::URI
  67. . 'search?q='
  68. . urlencode($this->getInput('q'))
  69. . '&f=tweets';
  70. case 'By username':
  71. return self::URI
  72. . urlencode($this->getInput('u'));
  73. // Always return without replies!
  74. // . ($this->getInput('norep') ? '' : '/with_replies');
  75. default: return parent::getURI();
  76. }
  77. }
  78. public function collectData(){
  79. $html = '';
  80. $html = getSimpleHTMLDOM($this->getURI());
  81. if(!$html) {
  82. switch($this->queriedContext) {
  83. case 'By keyword or hashtag':
  84. returnServerError('No results for this query.');
  85. case 'By username':
  86. returnServerError('Requested username can\'t be found.');
  87. }
  88. }
  89. $hidePictures = $this->getInput('nopic');
  90. foreach($html->find('div.js-stream-tweet') as $tweet) {
  91. // Skip retweets?
  92. if($this->getInput('noretweet')
  93. && $tweet->getAttribute('data-screen-name') !== $this->getInput('u')) {
  94. continue;
  95. }
  96. // remove 'invisible' content
  97. foreach($tweet->find('.invisible') as $invisible) {
  98. $invisible->outertext = '';
  99. }
  100. $item = array();
  101. // extract username and sanitize
  102. $item['username'] = $tweet->getAttribute('data-screen-name');
  103. // extract fullname (pseudonym)
  104. $item['fullname'] = $tweet->getAttribute('data-name');
  105. // get author
  106. $item['author'] = $item['fullname'] . ' (@' . $item['username'] . ')';
  107. // get avatar link
  108. $item['avatar'] = $tweet->find('img', 0)->src;
  109. // get TweetID
  110. $item['id'] = $tweet->getAttribute('data-tweet-id');
  111. // get tweet link
  112. $item['uri'] = self::URI . substr($tweet->find('a.js-permalink', 0)->getAttribute('href'), 1);
  113. // extract tweet timestamp
  114. $item['timestamp'] = $tweet->find('span.js-short-timestamp', 0)->getAttribute('data-time');
  115. // generate the title
  116. $item['title'] = strip_tags($this->fixAnchorSpacing($tweet->find('p.js-tweet-text', 0), '<a>'));
  117. $this->processContentLinks($tweet);
  118. $this->processEmojis($tweet);
  119. // get tweet text
  120. $cleanedTweet = str_replace(
  121. 'href="/',
  122. 'href="' . self::URI,
  123. $tweet->find('p.js-tweet-text', 0)->innertext
  124. );
  125. // fix anchors missing spaces in-between
  126. $cleanedTweet = $this->fixAnchorSpacing($cleanedTweet);
  127. // Add picture to content
  128. $picture_html = '';
  129. if(!$hidePictures) {
  130. $picture_html = <<<EOD
  131. <a href="https://twitter.com/{$item['username']}">
  132. <img
  133. style="align:top; width:75px; border:1px solid black;"
  134. alt="{$item['username']}"
  135. src="{$item['avatar']}"
  136. title="{$item['fullname']}" />
  137. </a>
  138. EOD;
  139. }
  140. // Add embeded image to content
  141. $image_html = '';
  142. $image = $this->getImageURI($tweet);
  143. if(!$this->getInput('noimg') && !is_null($image)) {
  144. // add enclosures
  145. $item['enclosures'] = array($image . ':orig');
  146. $image_html = <<<EOD
  147. <a href="{$image}:orig">
  148. <img
  149. style="align:top; max-width:558px; border:1px solid black;"
  150. src="{$image}:thumb" />
  151. </a>
  152. EOD;
  153. }
  154. // add content
  155. $item['content'] = <<<EOD
  156. <div style="display: inline-block; vertical-align: top;">
  157. {$picture_html}
  158. </div>
  159. <div style="display: inline-block; vertical-align: top;">
  160. <blockquote>{$cleanedTweet}</blockquote>
  161. </div>
  162. <div style="display: block; vertical-align: top;">
  163. <blockquote>{$image_html}</blockquote>
  164. </div>
  165. EOD;
  166. // add quoted tweet
  167. $quotedTweet = $tweet->find('div.QuoteTweet', 0);
  168. if($quotedTweet) {
  169. // get tweet text
  170. $cleanedQuotedTweet = str_replace(
  171. 'href="/',
  172. 'href="' . self::URI,
  173. $quotedTweet->find('div.tweet-text', 0)->innertext
  174. );
  175. $this->processContentLinks($quotedTweet);
  176. $this->processEmojis($quotedTweet);
  177. // Add embeded image to content
  178. $quotedImage_html = '';
  179. $quotedImage = $this->getQuotedImageURI($tweet);
  180. if(!$this->getInput('noimg') && !is_null($quotedImage)) {
  181. // add enclosures
  182. $item['enclosures'] = array($quotedImage . ':orig');
  183. $quotedImage_html = <<<EOD
  184. <a href="{$quotedImage}:orig">
  185. <img
  186. style="align:top; max-width:558px; border:1px solid black;"
  187. src="{$quotedImage}:thumb" />
  188. </a>
  189. EOD;
  190. }
  191. $item['content'] = <<<EOD
  192. <div style="display: inline-block; vertical-align: top;">
  193. <blockquote>{$cleanedQuotedTweet}</blockquote>
  194. </div>
  195. <div style="display: block; vertical-align: top;">
  196. <blockquote>{$quotedImage_html}</blockquote>
  197. </div>
  198. <hr>
  199. {$item['content']}
  200. EOD;
  201. }
  202. // put out
  203. $this->items[] = $item;
  204. }
  205. }
  206. private function processEmojis($tweet){
  207. // process emojis (reduce size)
  208. foreach($tweet->find('img.Emoji') as $img) {
  209. $img->style .= ' height: 1em;';
  210. }
  211. }
  212. private function processContentLinks($tweet){
  213. // processing content links
  214. foreach($tweet->find('a') as $link) {
  215. if($link->hasAttribute('data-expanded-url')) {
  216. $link->href = $link->getAttribute('data-expanded-url');
  217. }
  218. $link->removeAttribute('data-expanded-url');
  219. $link->removeAttribute('data-query-source');
  220. $link->removeAttribute('rel');
  221. $link->removeAttribute('class');
  222. $link->removeAttribute('target');
  223. $link->removeAttribute('title');
  224. }
  225. }
  226. private function fixAnchorSpacing($content){
  227. // fix anchors missing spaces in-between
  228. return str_replace(
  229. '<a',
  230. ' <a',
  231. $content
  232. );
  233. }
  234. private function getImageURI($tweet){
  235. // Find media in tweet
  236. $container = $tweet->find('div.AdaptiveMedia-container', 0);
  237. if($container && $container->find('img', 0)) {
  238. return $container->find('img', 0)->src;
  239. }
  240. return null;
  241. }
  242. private function getQuotedImageURI($tweet){
  243. // Find media in tweet
  244. $container = $tweet->find('div.QuoteMedia-container', 0);
  245. if($container && $container->find('img', 0)) {
  246. return $container->find('img', 0)->src;
  247. }
  248. return null;
  249. }
  250. }