diff --git a/README.md b/README.md
index 77be8cf..8174954 100644
--- a/README.md
+++ b/README.md
@@ -6,36 +6,31 @@ rss-bridge is a collection of independant php scripts capable of generating ATOM
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:
+ * `FlickrExplore` : [Latest interesting images](http://www.flickr.com/explore) from Flickr.
+ * `GoogleSearch` : Most recent results from Google Search. Parameters:
* q=keyword : Keyword search.
- * `rss-bridge-twitter.php` : Twitter. Parameters:
+ * `Twitter.php` : Twitter. Parameters:
* q=keyword : Keyword search.
* u=username : Get user timeline.
+Easy new bridge system (detail below) !
+
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.
+ * `Atom` : ATOM Feed.
+ * `Json` : jSon
+ * `Html` : html page
+ * `Plaintext` : raw text (php object, as returned by print_r)
+Easy new format system (detail below) !
Requirements
===
* php 5.3
- * [PHP Simple HTML DOM Parser](http://simplehtmldom.sourceforge.net/)
+ * [PHP Simple HTML DOM Parser](http://simplehtmldom.sourceforge.net)
Author
===
@@ -44,7 +39,7 @@ I'm sebsauvage, webmaster of [sebsauvage.net](http://sebsauvage.net), author of
Thanks to [Mitsukarenai](https://github.com/Mitsukarenai) for the inspiration.
Patch :
-- Yves ASTIER (Draeli) : PHP optimizations, minor fixes, dynamic brigde list with all stuff behind
+- Yves ASTIER (Draeli) : PHP optimizations, minor fixes, dynamic brigde/format list with all stuff behind and extend cache system. Mail : contact@yves-astier.com
Licence
===
@@ -54,7 +49,7 @@ 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).
+ * To implement a new rss-bridge, create a new class in `bridges` directory and extends with `BridgeAbstract`. 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). If you want your new bridge appear in `index.php`, don't forget add annotation.
Rant
===
diff --git a/bridges/FlickrExploreBridge.php b/bridges/FlickrExploreBridge.php
new file mode 100644
index 0000000..c3b4976
--- /dev/null
+++ b/bridges/FlickrExploreBridge.php
@@ -0,0 +1,35 @@
+returnError('Could not request Flickr.', 404);
+
+ foreach($html->find('span.photo_container') as $element) {
+ $item = new \Item();
+ $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;
+ }
+ }
+
+ public function getName(){
+ return 'Flickr Explore';
+ }
+
+ public function getURI(){
+ return 'http://www.flickr.com/explore';
+ }
+
+ public function getCacheDuration(){
+ return 21600; // 6 hours
+ }
+}
\ No newline at end of file
diff --git a/bridges/GoogleSearchBridge.php b/bridges/GoogleSearchBridge.php
new file mode 100644
index 0000000..79fcc40
--- /dev/null
+++ b/bridges/GoogleSearchBridge.php
@@ -0,0 +1,51 @@
+returnError('No results for this query.', 404);
+ }
+ else{
+ $this->returnError('You must specify a keyword (?q=...).', 400);
+ }
+
+ $emIsRes = $html->find('div[id=ires]',0);
+ if( !is_null($emIsRes) ){
+ foreach($emIsRes->find('li[class=g]') as $element) {
+ $item = new \Item();
+ $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;
+ }
+ }
+ }
+
+ public function getName(){
+ return 'Google search';
+ }
+
+ public function getURI(){
+ return 'http://google.com';
+ }
+
+ public function getCacheDuration(){
+ return 1800; // 30 minutes
+ }
+}
\ No newline at end of file
diff --git a/bridges/TwitterBridge.php b/bridges/TwitterBridge.php
new file mode 100644
index 0000000..53766f1
--- /dev/null
+++ b/bridges/TwitterBridge.php
@@ -0,0 +1,50 @@
+returnError('No results for this query.', 404);
+ }
+ elseif (isset($param['u'])) { /* user timeline mode */
+ $html = file_get_html('http://twitter.com/'.urlencode($param['u'])) or $this->returnError('Requested username can\'t be found.', 404);
+ }
+ else {
+ $this->returnError('You must specify a keyword (?q=...) or a Twitter username (?u=...).', 400);
+ }
+
+ foreach($html->find('div.tweet') as $tweet) {
+ $item = new \Item();
+ $item->username = trim(substr($tweet->find('span.username', 0)->plaintext, 1)); // extract username and sanitize
+ $item->fullname = $tweet->getAttribute('data-name'); // extract fullname (pseudonym)
+ $item->avatar = $tweet->find('img', 0)->src; // get avatar link
+ $item->id = $tweet->getAttribute('data-tweet-id'); // get TweetID
+ $item->uri = 'https://twitter.com'.$tweet->find('a.details', 0)->getAttribute('href'); // get tweet link
+ $item->timestamp = $tweet->find('span._timestamp', 0)->getAttribute('data-time'); // extract tweet timestamp
+ $item->content = str_replace('href="/', 'href="https://twitter.com/', strip_tags($tweet->find('p.tweet-text', 0)->innertext, '')); // extract tweet text
+ $item->title = $item->fullname . ' (@'. $item->username . ') | ' . $item->content;
+ $this->items[] = $item;
+ }
+ }
+
+ public function getName(){
+ return 'Twitter Bridge';
+ }
+
+ public function getURI(){
+ return 'http://twitter.com';
+ }
+
+ public function getCacheDuration(){
+ return 300; // 5 minutes
+ }
+}
\ No newline at end of file
diff --git a/bridges/flickr-explore.php b/bridges/flickr-explore.php
deleted file mode 100644
index 6acfeab..0000000
--- a/bridges/flickr-explore.php
+++ /dev/null
@@ -1,29 +0,0 @@
-returnError(404, '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;
- }
- }
-}
-
-$bridge = new RssBridgeFlickrExplore();
-$bridge->process();
\ No newline at end of file
diff --git a/bridges/googlesearch.php b/bridges/googlesearch.php
deleted file mode 100644
index 9ef0c04..0000000
--- a/bridges/googlesearch.php
+++ /dev/null
@@ -1,41 +0,0 @@
-returnError(404, 'no results for this query.');
- } else {
- $this->returnError(400, '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/bridges/twitter.php b/bridges/twitter.php
deleted file mode 100644
index 8423cef..0000000
--- a/bridges/twitter.php
+++ /dev/null
@@ -1,42 +0,0 @@
-returnError(404, 'no results for this query.');
- } elseif (isset($request['u'])) { /* user timeline mode */
- $html = file_get_html('http://twitter.com/'.urlencode($request['u'])) or $this->returnError(404, 'requested username can\'t be found.');
- } else {
- $this->returnError(400, 'You must specify a keyword (?q=...) or a Twitter username (?u=...).');
- }
- $this->items = Array();
- foreach($html->find('div.tweet') as $tweet) {
- $item['username'] = trim(substr($tweet->find('span.username', 0)->plaintext, 1)); // extract username and sanitize
- $item['fullname'] = $tweet->getAttribute('data-name'); // extract fullname (pseudonym)
- $item['avatar'] = $tweet->find('img', 0)->src; // get avatar link
- $item['id'] = $tweet->getAttribute('data-tweet-id'); // get TweetID
- $item['uri'] = 'https://twitter.com'.$tweet->find('a.details', 0)->getAttribute('href'); // get tweet link
- $item['timestamp'] = $tweet->find('span._timestamp', 0)->getAttribute('data-time'); // extract tweet timestamp
- $item['content'] = str_replace('href="/', 'href="https://twitter.com/', strip_tags($tweet->find('p.tweet-text', 0)->innertext, '')); // extract tweet text
- $item['title'] = $item['fullname'] . ' (@'.$item['username'] . ') | ' . $item['content'];
- $this->items[] = $item;
- }
- }
-}
-
-$bridge = new RssBridgeTwitter();
-$bridge->process();
\ No newline at end of file
diff --git a/caches/FileCache.php b/caches/FileCache.php
new file mode 100644
index 0000000..594343d
--- /dev/null
+++ b/caches/FileCache.php
@@ -0,0 +1,92 @@
+isPrepareCache();
+
+ $datas = json_decode(file_get_contents($this->getCacheFile()),true);
+ $items = array();
+ foreach($datas as $aData){
+ $item = new \Item();
+ foreach($aData as $name => $value){
+ $item->$name = $value;
+ }
+ $items[] = $item;
+ }
+
+ return $items;
+ }
+
+ public function saveData($datas){
+ $this->isPrepareCache();
+
+ file_put_contents($this->getCacheFile(), json_encode($datas));
+
+ return $this;
+ }
+
+ public function getTime(){
+ $this->isPrepareCache();
+
+ $cacheFile = $this->getCacheFile();
+ if( file_exists($cacheFile) ){
+ return filemtime($cacheFile);
+ }
+
+ return false;
+ }
+
+ /**
+ * Cache is prepared ?
+ * Note : Cache name is based on request information, then cache must be prepare before use
+ * @return \Exception|true
+ */
+ protected function isPrepareCache(){
+ if( is_null($this->param) ){
+ throw new \Exception('Please feed "prepare" method before try to load');
+ }
+
+ return true;
+ }
+
+ /**
+ * Return cache path (and create if not exist)
+ * @return string Cache path
+ */
+ protected function getCachePath(){
+ $cacheDir = __DIR__ . '/../cache/'; // FIXME : configuration ?
+
+ // FIXME : implement recursive dir creation
+ if( is_null($this->cacheDirCreated) && !is_dir($cacheDir) ){
+ $this->cacheDirCreated = true;
+
+ mkdir($cacheDir,0705);
+ chmod($cacheDir,0705);
+ }
+
+ return $cacheDir;
+ }
+
+ /**
+ * Get the file name use for cache store
+ * @return string Path to the file cache
+ */
+ protected function getCacheFile(){
+ return $this->getCachePath() . $this->getCacheName();
+ }
+
+ /**
+ * Determines file name for store the cache
+ * return string
+ */
+ protected function getCacheName(){
+ $this->isPrepareCache();
+
+ $stringToEncode = $_SERVER['REQUEST_URI'] . http_build_query($this->param);
+ return hash('sha1', $stringToEncode) . '.cache';
+ }
+}
\ No newline at end of file
diff --git a/formats/AtomFormat.php b/formats/AtomFormat.php
new file mode 100644
index 0000000..2df9090
--- /dev/null
+++ b/formats/AtomFormat.php
@@ -0,0 +1,79 @@
+getExtraInfos();
+ $title = htmlspecialchars($extraInfos['name']);
+ $uri = htmlspecialchars($extraInfos['uri']);
+
+ $entries = '';
+ foreach($this->getDatas() as $data){
+ $entryName = is_null($data->name) ? $title : $data->name;
+ $entryAuthor = is_null($data->author) ? $uri : $data->author;
+ $entryTitle = is_null($data->title) ? '' : $data->title;
+ $entryUri = is_null($data->uri) ? '' : $data->uri;
+ $entryTimestamp = is_null($data->timestamp) ? '' : date(DATE_ATOM, $data->timestamp);
+ $entryContent = is_null($data->content) ? '' : 'content) . ']]>';
+
+ $entries .= <<
+
+ {$entryName}
+ {$entryAuthor}
+
+
+
+ {$entryUri}
+ {$entryTimestamp}
+ {$entryContent}
+
+
+EOD;
+ }
+
+ /*
+ TODO :
+ - Security: Disable Javascript ?
+ - : Define new extra info ?
+ - : RFC look with xhtml, keep this in spite of ?
+ */
+
+ /* Data are prepared, now let's begin the "MAGIE !!!" */
+ $toReturn = '';
+ $toReturn .= <<
+
+ {$title}
+ http{$https}://{$httpHost}{$httpInfo}/
+
+
+
+{$entries}
+
+EOD;
+
+ return $toReturn;
+ }
+
+ public function display(){
+ // $this
+ // ->setContentType('application/atom+xml; charset=' . $this->getCharset())
+ // ->callContentType();
+
+ return parent::display();
+ }
+}
\ No newline at end of file
diff --git a/formats/HtmlFormat.php b/formats/HtmlFormat.php
new file mode 100644
index 0000000..86c6173
--- /dev/null
+++ b/formats/HtmlFormat.php
@@ -0,0 +1,62 @@
+getExtraInfos();
+ $title = htmlspecialchars($extraInfos['name']);
+ $uri = htmlspecialchars($extraInfos['uri']);
+
+ $entries = '';
+ foreach($this->getDatas() as $data){
+ $entryUri = is_null($data->uri) ? $uri : $data->uri;
+ $entryTitle = is_null($data->title) ? '' : htmlspecialchars(strip_tags($data->title));
+ $entryTimestamp = is_null($data->timestamp) ? '' : '' . date(DATE_ATOM, $data->timestamp) . '';
+ $entryContent = is_null($data->content) ? '' : '' . $data->content . '
';
+
+ $entries .= <<