diff --git a/.gitignore b/.gitignore
index 172d97a9..e14b1fbd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
## Eclipse
#################
simple_html_dom.php
+data/
*.pydevproject
.project
.metadata
diff --git a/README.md b/README.md
index 5450c313..5ba12f66 100644
--- a/README.md
+++ b/README.md
@@ -7,17 +7,52 @@ Supported sites/pages
===
* `rss-bridge-flickr-explore.php` : [Latest interesting images](http://www.flickr.com/explore) from Flickr.
+ * `rss-bridge-googlesearch.php` : Most recent results from Google Search. Parameters:
+ * q=keyword : Keyword search.
+ * `rss-bridge-twitter.php` : Twitter. Parameters:
+ * q=keyword : Keyword search.
+ * u=username : Get user timeline.
+Output format
+===
+Output format can be used in any rss-bridge:
+
+ * `format=atom` (default): ATOM Feed.
+ * `format=json` : jSon
+ * `format=html` : html page
+ * `format=plaintext` : raw text (php object, as returned by print_r)
+
+If format is not specified, ATOM format will be used.
+
+Examples
+===
+ * `rss-bridge-twitter.php?u=Dinnerbone` : Get Dinnerbone (Minecraft developer) timeline, in ATOM format.
+ * `rss-bridge-twitter.php?q=minecraft&format=html` : Everything Minecraft from Twitter, in html format.
+ * `rss-bridge-flickr-explore.php` : Latest interesting images from Flickr, in ATOM format.
+
+
Requirements
===
* php 5.3
* [PHP Simple HTML DOM Parser](http://simplehtmldom.sourceforge.net/)
+Author
+===
+I'm sebsauvage, webmaster of [sebsauvage.net](http://sebsauvage.net), author of [Shaarli](http://sebsauvage.net/wiki/doku.php?id=php:shaarli) and [ZeroBin](http://sebsauvage.net/wiki/doku.php?id=php:zerobin).
+
+Thanks to [Mitsukarenai](https://github.com/Mitsukarenai) for the inspiration.
+
Licence
===
Code is public domain.
+
+Technical notes
+===
+ * There is a cache so that source services won't ban you even if you hammer the rss-bridge with requests. Each bridge has a different duration for the cache. The `cache` subdirectory will be automatically created. You can purge it whenever you want.
+ * To implement a new rss-bridge, import `rss-bridge-lib.php` and subclass `RssBridgeAbstractClass`. Look at existing bridges for examples. For items you generate in `$this->items`, only `uri` and `title` are mandatory in each item. `timestamp` and `content` are optional but recommended. Any additional key will be ignored by ATOM feed (but outputed to jSon).
+
Rant
===
@@ -29,4 +64,6 @@ You're not social when you hamper sharing by removing RSS. You're happy to have
We want to share with friends, using open protocols: RSS, XMPP, whatever. Because no one wants to have *your* service with *your* applications using *your* API forced-feeded to them. Friends must be free to choose whatever software and service they want.
+We are rebuilding bridges your have wilfully destroyed.
+
Get your shit together: Put RSS back in.
\ No newline at end of file
diff --git a/rss-bridge-flickr-explore.php b/rss-bridge-flickr-explore.php
index 09f0fef0..e7f153eb 100644
--- a/rss-bridge-flickr-explore.php
+++ b/rss-bridge-flickr-explore.php
@@ -1,66 +1,29 @@
find('span.photo_container') as $element)
+/**
+ * RssBridgeFlickrExplore
+ * Returns the newest interesting images from http://www.flickr.com/explore
+ */
+class RssBridgeFlickrExplore extends RssBridgeAbstractClass
{
- $item['href'] = 'http://flickr.com'.$element->find('a',0)->href; // Page URI
- $item['thumbnailUri'] = $element->find('img',0)->getAttribute('data-defer-src'); // Thumbnail URI
- $item['title'] = $element->find('a',0)->title; // Photo title
- $items[] = $item;
-}
-
-if(empty($items)) { returnError('404 Not Found', 'ERROR: no results.'); }
-$format = 'atom';
-if (!empty($_GET['format'])) { $format = $_GET['format']; }
-switch($format)
-{
- case 'plaintext':
- case 'json':
- case 'atom':
- break;
- default:
- $format='atom';
-}
-
-if($format == 'plaintext') { header('content-type: text/plain;charset=utf8'); print_r($items); exit; }
-if($format == 'json') { header('content-type: application/json'); $items = json_encode($items); exit($items); }
-if($format == 'atom')
-{
- header('content-type: application/atom+xml; charset=UTF-8');
- echo ''."\n";
- echo 'Flickr Explore'."\n";
- echo 'http'.(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ? 's' : '')."://{$_SERVER['HTTP_HOST']}{$_SERVER['PATH_INFO']}".'/'."\n";
- echo ''.date(DATE_ATOM, $tweets['0']['timestamp']).''."\n";
- echo ''."\n";
- echo ''."\n"."\n";
-
- foreach($items as $item) {
- echo 'Flickrhttp://flickr.com/'."\n";
- echo ''."\n";
- echo ''."\n";
- echo ''.$item['href'].''."\n";
- echo ''."\n"; // FIXME: date ???
- echo ']]>'."\n";
- echo ''."\n\n";
+ protected $bridgeName = 'Flickr Explore';
+ protected $bridgeURI = 'http://www.flickr.com/explore';
+ protected $bridgeDescription = 'Returns the latest interesting images from Flickr';
+ protected $cacheDuration = 360; // 6 hours. No need to get more.
+ protected function collectData($request) {
+ $html = file_get_html('http://www.flickr.com/explore') or $this->returnError('404 Not Found', 'ERROR: could not request Flickr.');
+ $this->items = Array();
+ foreach($html->find('span.photo_container') as $element) {
+ $item['uri'] = 'http://flickr.com'.$element->find('a',0)->href;
+ $item['thumbnailUri'] = $element->find('img',0)->getAttribute('data-defer-src');
+ $item['content'] = ''; // FIXME: Filter javascript ?
+ $item['title'] = $element->find('a',0)->title;
+ $this->items[] = $item;
}
- echo '';
- exit;
+ }
}
-exit();
+$bridge = new RssBridgeFlickrExplore();
+$bridge->process();
+?>
\ No newline at end of file
diff --git a/rss-bridge-googlesearch.php b/rss-bridge-googlesearch.php
new file mode 100644
index 00000000..c19a6ca3
--- /dev/null
+++ b/rss-bridge-googlesearch.php
@@ -0,0 +1,41 @@
+returnError('404 Not Found', 'ERROR: no results for this query.');
+ } else {
+ $this->returnError('400 Bad Request', 'ERROR: You must specify a keyword (?q=...).');
+ }
+ $this->items = Array();
+ foreach($html->find('div[id=ires]',0)->find('li[class=g]') as $element) {
+ $item['uri'] = $element->find('a[href]',0)->href;
+ $item['title'] = $element->find('h3',0)->plaintext;
+ $item['content'] = $element->find('span[class=st]',0)->plaintext;
+ $this->items[] = $item;
+ }
+ }
+}
+
+$bridge = new RssBridgeGoogleSearch();
+$bridge->process();
+?>
\ No newline at end of file
diff --git a/rss-bridge-lib.php b/rss-bridge-lib.php
new file mode 100644
index 00000000..b4ff7f40
--- /dev/null
+++ b/rss-bridge-lib.php
@@ -0,0 +1,214 @@
+'http://foo.bar', 'title'=>'My beautiful foobar', 'content'='Hello, world !','timestamp'=>'1375864834'),
+ * Array('uri'=>'http://toto.com', 'title'=>'Welcome to toto', 'content'='What is this website about ?','timestamp'=>'1375868313')
+ * )
+ * Keys in dictionnaries:
+ * uri (string;mandatory) = The URI the item points to.
+ * title (string;mandatory) = Title of item
+ * content (string;optionnal) = item content (usually HTML code)
+ * timestamp (string;optionnal) = item date. Must be in EPOCH format.
+ * Other keys can be added, but will be ignored.
+ * $items will be used to build the ATOM feed, json and other outputs.
+ */
+ var $items;
+
+ private $contentType; // MIME type returned to browser.
+
+ /**
+ * Sets the content-type returns to browser.
+ * Example: $this->setContentType('text/html; charset=UTF-8')
+ */
+ private function setContentType($value)
+ {
+ $this->contentType = $value;
+ header('Content-Type: '.$value);
+ }
+
+ /**
+ * collectData() will be called to ask the bridge to go collect data on the net.
+ * All derived classes must implement this method.
+ * This method must fill $this->items with collected items.
+ * Input: $request : The incoming request (=$_GET). This can be used or ignored by the bridge.
+ */
+ abstract protected function collectData($request);
+
+ /**
+ * Returns a HTTP error to user, with a message.
+ * Example: $this->returnError('404 Not Found', 'ERROR: no results.');
+ */
+ protected function returnError($code, $message)
+ {
+ header("HTTP/1.1 $code"); header('Content-Type: text/plain;charset=UTF-8');
+ die($message);
+ }
+
+ /**
+ * Builds an ATOM feed from $this->items and return it to browser.
+ */
+ private function returnATOM()
+ {
+ $this->setContentType('application/atom+xml; charset=UTF-8');
+ echo ''."\n";
+ echo ''.htmlspecialchars($this->bridgeName).''."\n";
+ echo 'http'.(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ? 's' : '')."://{$_SERVER['HTTP_HOST']}{$_SERVER['PATH_INFO']}".'/'."\n";
+ echo ''."\n"; // FIXME
+ echo ''."\n";
+ echo ''."\n"."\n";
+
+ foreach($this->items as $item) {
+ echo ''.htmlspecialchars($this->bridgeName).''.htmlspecialchars($this->bridgeURI).''."\n";
+ echo ''."\n";
+ echo ''."\n";
+ echo ''.$item['uri'].''."\n";
+ if (isset($item['timestamp']))
+ {
+ echo ''.date(DATE_ATOM, $item['timestamp']).''."\n";
+
+ }
+ else
+ {
+ echo ''."\n";
+ }
+ if (isset($item['content']))
+ {
+ echo ''."\n";
+ }
+ else
+ {
+ echo ''."\n";
+ }
+ // FIXME: Security: Disable Javascript ?
+ echo ''."\n\n";
+ }
+ echo '';
+ }
+
+ private function returnHTML()
+ {
+ $this->setContentType('text/html; charset=UTF-8');
+ echo '