<?php /*

Facebook StreamFeed 
BETA 20100114
Copyright (c) 2009 Nato Welch 
Email: nato at streamfeeddev.r30.net
All rights reserved. This software is BSD-licensed; please see the LICENSE section at the end of this file.

Don't leave Facebook without your data! [[http://planetdone.com/facebook/streamfeed.php StreamFeed]] is an open-source [[http://dataportability.org data portability]] tool that allows you to export your Facebook activity stream into popular syndication formats like [[http://en.wikipedia.org/wiki/RSS_%28file_format%29 RSS]] and [[http://en.wikipedia.org/wiki/Atom_%28standard%29 Atom]], by way of the new [[http://wiki.developers.facebook.com/index.php/Using_the_Open_Stream_API  Facebook Open Stream API]].

INSTALLATION:
Currently, StreamFeed requires the official Facebook PHP client libraries:
http://wiki.developers.facebook.com/index.php/PHP
StreamFeed will be re-written to include this functionality directly in the future
in order to keep it monolithic and easy to install elsewhere. 
  
There is also a require()'ed configuration file: streamfeedconfig.inc.php
This contains the Facebook application secret required to cash in auth_tokens to initiate sessions.
Its contents are simply:

<?php return array( 
  'apikey' => '[your apikey here]',
  'appsecret' => '[your app secret here]' ) ;
?>

In order to get your own apikey and app secret, 
Install the Facebook Developer application on your Facebook profile
and create an application: 
http://developers.facebook.com/get_started.php


FEATURES:
subpaths:
subpaths are appended to the end of the streamfeed.php script (instead of query string parameters)

* /rss2.xml
  RSS 2.0 format support

* /auth
  For use by users calling the feed with HTTP authentication parameters. 
  Some user agents won't send the credentials unless first challenged by the web server. 
  Since authentication is optional, we need to know when to send a challenge. 

* /streamfeed.phps
  Displays the script's source code, in case people would like to download and install the code elsewhere.



##TODO:

* /atom.xml , /rss1.xml , etc. 
  support different syndication formats
  support activitystreams atom extension http://activitystrea.ms/
  (the way Facebook alleges it does, but doesn't)

* Eliminate facebook PHP library dependencies

* Fetch recent activity/notifications for slip-streaming into the stream

*/

$debug = ( $_REQUEST['debug'] ? true false ) ;


# comment this line to set debug mode on or off from URL parameters. 
# uncomment this line to hard-wire the debug mode (on or off). 
#$debug = 0 ;

# known post and media types
# tell me if I get a post type I'm not familiar with, so I can figure out how to render it.
$posttypes = array( 11804656237259662472361379279,
  
829412128273 ) ;
$mediatypes = array( 'photo''link''video''swf''music' ) ;

ob_start() ;

## DEFINE functions

function bugdump() {
  global 
$debug ;
  if ( 
$debug ) {
    echo 
'<pre>'var_dumpfunc_get_args() ) ; echo '</pre>' ;
    return 
$data ;
  } else { return 
false ; }
}
function 
buglog () { bugdumpfunc_get_args() ) ; }

function 
baseurl $subpath '' $credentialed true ) {
  global 
$iscred$feedid$uid$userinfo ;
  
  
$baseurl "http". ( $_SERVER['HTTPS'] == 'on' 's' '' ) ."://"
    
.( ( $iscred and $credentialed and $uid 
      ? ( 
strlen$userinfo['name'] ) ? $userinfo['name'] : $uid )
        .
':'substr$feedid010 ) .'@' 
      
'' )
    . 
$_SERVER['HTTP_HOST']
    . 
$_SERVER['SCRIPT_NAME'
    .( ( 
$iscred and $credentialed and $uid 
      ? 
'/auth' '' )
    . 
$subpath 
    
.( ( $iscred and $credentialed and $uid 
      ? 
'?feedid='substr$feedid10 )
      : 
'' )
    ;
  
  return 
$baseurl ;
}

function 
renderstreamhtml () {
  
extract$GLOBALS ) ;

  
$baseurl baseurl() ;

  
$rssiconurl 'data:image/gif;base64,R0lGODlhDwAPAKU+AOzJlu+YR+R2GuyaL+e3ZsK/rv6xI/2kF/qLAf50AOJrAOymY+NlALy6qeJzAOJ7AP6rHfjly/OaE+i1e/dvAO5rAPt6AP6ZDOaCAvaEAOyDAOt3APj08euXEtGcVOyjOv63KPuSBv+fErq4p7u3p9+aQvDXxvZ1APa6Rum4iOyjKul5APDAevO2Z+uQDfS5UeZ+IMiJS9JZANGNScCymdaxgdyzl/fRjfO6ftNkAPyFAPOEHe6NBuJfAP///////yH5BAEAAD8ALAAAAAAPAA8AAAalwN8H8ygai5jPTyghAAA3FGg6lQx+DwPBx/VFXoaw4VFwQFQeT63Lgrgd5cOh05mnOL6JHO4QubotEiV4HyJwChc8XT4AIRNeFwoFCiEhCBgxeAsaXAOSCggIGqEzXBmPOJ8ZXTorXBsBPimSDDBdGxYmPjmxKQwFDBVdJxYwASc7Ngu/MBUVMhQJ0tMUFQIFPwI929zdAjTYPw3jI+UjJCTlDT9BADs=' ;
  
  
$faviconurl 'data:;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAD9/PwA07msaaVwVduYXDz8mFs7/5hbO/+YWzv/mFs7/5hbO/+YWzv/mFs7/5hbO/+YXDz8pXBV29O5rGn9/PwA18CzXphbO/2ibE//qHVa/5hbO/+YWzv/mFs7/51kRv/Fo5H/mFs7/5hbO/+YWzv/mFs7/5hbO/+YWzv918CzXqp4Xs+ibE//+fb1//7+/f+1iXL/mFs7/5hbO/+kblL//v7+/7iPef+YWzv/mFs7/5hbO/+YWzv/mFs7/6p4Xs+YXDz6qHVa//7+/f//////vpiE/5hbO/+YWzv/xaOR//////+8lH//rn5l/5xiQ/+YWzv/mFs7/5hbO/+YXDz6mFs7/5hbO/+1iXL/vpiE/5lcPf+YWzv/top0//r39f/9/Pz/pW9T/8mpmP/p3NX/mFs7/5hbO/+YWzv/mFs7/5hbO/+YWzv/r4Bn/9vFuv/p3db/9fDt///////+/v7/yqua/5hbO//h0Mf//Pv6/5hbO/+jbVD/mFs7/5hbO/+YWzv/qnlf//v5+P/+/v7/5NXM/8yun//BnYr/rHph/5hbO/+8lH///v7+/+vg2v+YWzv/6+Da/7CCav+YWzv/mFs7/9e/s///////yqyb/5hbO/+reV//x6aV/9K4qv/l1c3//v7+//79/f+4j3n/n2ZI//37+//SuKr/mFs7/5hbO//m18//9O7r/5ldPv+8lH///v39//7+/v/y6+f/49LK/9e/s/+zh3D/mFs7/9K3qf//////x6aU/5hbO/+YWzv/zK6e/+DPxf+ZXD3/9fDt//39/f++mIT/mFs7/6NsUP+0h3D/v5mF/93KwP/+/v7/+fb0/6NsUP+YWzv/mFs7/5hbO/+tfWT/pnFV///////Zwrf/mFs7/86xov/+/f3///////79/f/38/D/5dfP/6+BaP+YWzv/mFs7/5hbO/+YWzv/mFs7/6JrTv/6+Pb/u5N+/7GDa//+/v7/+PTy/7qRfP+cYkP/mFs7/5hbO/+YXDz/mFs7/5hbO/+YXDz6mFs7/5hbO/+YWzv/rX1j/69/Z//OsaL//////7qSfP+YWzv/mFs7/5hbO//fzMP/+/n4/7mRe/+YXDz6qnhez5hbO/+YWzv/mFs7/5hbO/+YWzv/0bao//r39v+ZXT3/mFs7/5hbO/+aX0D//fz7///////XwLT/qnhez9fAs16YWzv9mFs7/5hbO/+YWzv/mFs7/55lSP/Ywrb/mFs7/5hbO/+YWzv/mFs7/76YhP/axLn/pnJW/dfAs179/PwA07msaaVwVduYXDz8mFs7/5hbO/+YWzv/mFw8/5hbO/+YWzv/mFs7/5hbO/+YXDz8pXBV29O5rGn9/PwAwAMAAIABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAAwAMAAA==' ;
  
  
$streamfeediconurl "data:image/gif;base64,R0lGODlhMgAyAOfxAC5QkS9RkS9RkjBRkjBSkjFTkjFTkzJTkzJUkzNUkzNUlDNVlDRVlDVVlDRWlDVWlDVWlTZWlTZXlTdXlTdXljdYljhYljhZljlZljlZlzlalzpalzpamDpbmDtbmDxcmD1cmT1dmT5dmT5emT5emj9emj9fmkBfmkBfm0Ffm0Fgm0Jhm0JhnENinERinURjnUVjnUZknUdlnkhmn0lnn0pnoEtooExooUtpoUxpoU1qoU5rok5sok9sok9so1BsolBso1Bto1Fto1JupFJvpFNvpFRwpVVwpVVxplZxpldypll1qFt1qFx3qV13ql94qmB6q2F6rGF7rGJ8rGN8rWN9rWV+rWV+rmZ/rmZ/r2iAr2qDsWuDsW2Fsm6Gs3CHtHCItHGItHKItHKItXKJtHKJtXOJtXSLtnWLtnaMt3aNt3qPuXqQuXuRun2Tu4CUvICVvIGVvYGWvYKWvYKXvYOXvYOXvoSYvoWZv4aav4mcwIqdwYuewYuewo2fw4+gw4+hxJCixJCjxJGjxZKjxZKkxZKkxpOkxpOlx5Slx5Wmx5WmyJanyJipyZmqyZysy5yty56uzJ+uzKCvzaKxzqOyzqSyz6Szz6a00KW10Ka10Ki20am30am30qq40qu50qu506271K671a681K681bC91bG+1rK+1rO+1rPA17TA17XB2LnF2rrF2rrG2rvH3L3H3L7I3b/K3cHL3sHM3sPN38TN4MbP4cnR4snS4srS48rT48zU5M7W5dDX5tHY59DZ59HZ59Pa6NXc6dbc6dfe6tjf6trg69rg7Nrh69zh7Nzi7d/k7eHl7+Ln8OXp8ebq8ebq8ufr8ujs8+ns8+nt8+rt8+rt9Ozv9Ozv9e3v9u3w9e7x9u/y9/Dy9/Dz9/L0+PP1+PP1+fX3+vb3+vb4+vf4+vf4+/f5+/r7/Pr7/fv7/fz8/fz9/f39/v7+/v7//////////////////////////////////////////////////////////////yH+EUNyZWF0ZWQgd2l0aCBHSU1QACH5BAEKAP8ALAAAAAAyADIAAAj+AP8JHChwwwYPCBMqXMiwoUKCEAkaPOiwIYeKFQ1GHGgQY8ODFD0uPLixo0iFG5y4YnDSg4QED0B6gOghZMsKcOKZkiAywR1i1JAx4klSYM2WCScMiveuFE+MG0QxjUfVFgSEGwoiRSjBE9V4slhm3PG1bB+QWrcGKPdVmQGMF2KVpfrOF9F/WxMCmPsAo4Vhc6keu3q0ZYYUCAGwKwsHYwZJgd/ZwoDVZkUJXIwhJFDt67thCzJ+aPfOczwpWzdYkBMPFsIFueaisYzyS7qv6+xYQHpwFNVXCDMIKvvuHAjaCjPcWWVoBHKMvqYCS7gk8LgTzxFqwJAhu8MWU9/+QaPIIFnpsusKSWBwoUNejDDKggtpY6rpeLUatfEChcgIBLulhp0HL8wlBEUY+GFfYHOJQ0gNF7S0QRZ3RHjCXMpEmFAkDHZYGiUmVcQAIvF8kYEHGahjGhMUbXBBEc10GFk8s2joEAGqMNWFBh5ggMpc5MiAkgI9iPHHJrIMs42H8exBgUMQ8PLVGxFuUB9x0sRw4kIaUMAAAgNQ0AUrdM21g0MFlKXJUx5EM1dpZLhgQXfPZcACNQu+g0kFDYVQljYJJPTDjPEwU8kYQqDEEDaBEfDRXEEklIEjMqIXjjWffBCSQeOcRxUVDQUw1yIBeqAAJAvKeN4jDiS0AR7+cw3S0AHezCVkQg9gQQxxlcYziVgeAABOWaA0REEvnr4jzJZYRXABGp7s4ow03KBT5lx7UJTBIWWd4lAggRVTGEMTbeCDLZGl00BCWpQVikMRvBlPNkkc4J2LN0zjKVWpJKRDWZY4lMEcHT7TSiJnPDEDmAxYpkI0yX5DkQhfccKnQxLckmqH29DCRgk24fCmpjVRdYgCHm2wzMYythMFsxbEMdcVB5UQjxfMQmUHywyWFoZCK8ylx4kyHIEUBxmQMEQaneDi5n2ewUMDRResUlYiJ4a4VQYSPKDAASMo4cYvG3cjAlZMlAVIzhIi4AEDbLoqwBrp7PuOFRRp4M7CVLPltUEV11CViQq0bcDDYmUhA6w5pZFC2VYW8LGgO4STa0RgKCRETjx1eOcQEvJmEreruszlRELxTGEjb3l0CCxKJszlRUJArA5Vcors+9VlwXhqRmW8LXQGg9e47dAlZYHxXk3/hGSB7u9UkZ0aZW3xXlbNu2pDaefx4V0TU9HhuUMFbWrBGYrkgUSpDl0eTxm2I8WRZd2dBL4U45OL/UDjbnVFDsvTSETyR67lIWQjWiGgAROCQJr0b4GuEmBEAgIAOw==" ;

?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head><title><?=(strlen$name ) ? "{$name}'s " '' )?>Facebook StreamFeed</title>
  <link type="image/x-icon" href="<?=$faviconurl?>" rel="shortcut icon"/>
  <style><!--
    h1 { color: white ; background-color: #dddddd ; padding: 4px ; }
    a { text-decoration: none ; }
    table { border-collapse: collapse ; }
    img { border: none ; }
    .about { 
      border: thin solid #dddddd ; padding: 8px ; 
      display: <?= preg_match'/\.html$/'$subpath ) ? 'none' 'block' ?> ;
      }

    --></style>
  </head>
<body><a name='aboutstreamfeed'/><table width=800><tr><td>

<h1><img src='<?=$streamfeediconurl?>' style='vertical-align: middle'/> 
  <?=(strlen$name ) ? "{$name}'s " '' )?>Facebook StreamFeed beta</h1> 


<table width='100%'>
  <tr><td><table width='100%'><tr><td style='border-bottom: thin solid #dddddd ;'>

    <a id='aboutlink' onclick="document.getElementById('about').style.display = <?
      ?>
'block' ;" href='<?=baseurl'/about.html' )?>'>About StreamFeed</a>
      </td></tr></table></td></tr>

  <tr><td><table><tr><td id='about' class='streamfeedabout'>

<p>Welcome to StreamFeed! </p>

<p>Don't leave Facebook without your data! <a href="http://planetdone.com/facebook/streamfeed.php">StreamFeed</a> is an open-source <a href="http://dataportability.org">data portability</a> tool that allows you to export your Facebook activity stream into popular syndication formats like <a href="http://en.wikipedia.org/wiki/RSS_%28file_format%29">RSS</a> and <a href="http://en.wikipedia.org/wiki/Atom_%28standard%29">Atom</a>, by way of the new <a href="http://wiki.developers.facebook.com/index.php/Using_the_Open_Stream_API">Facebook Open Stream API</a>.  It's written in <a href='http://php.net/'>PHP</a> by <a href='http://n8o.r30.net/'>Nato Welch</a>.</p>

<p>StreamFeed attempts to strike an appropriate and elegant balance between privacy, security, and <a href='http://dataportability.org/'>data portability</a>. StreamFeed stores ZERO private user information on the server; everything is encoded into the authentication credentials embedded in the feed URLs. This makes the use of cookies or other tracking methods unnecessary. </p>

<p>StreamFeed offers Facebook activity streams in:</p>

<table border=0 cellpadding=6 >
  
  <tr><td  width=50 align='right'>&bull;</td>
    <? if ( $iscred ) { ?><td><a href='<?=baseurl'/rss2.xml' ?>'>
    <img src='<?=$rssiconurl?>'> /rss2.xml</a></td><? ?>
    <td align='left'>RSS 2.0</td>
    </tr>
  
  <tr><td width=50 align='right'>&bull;</td>
    <? if ( $iscred ) { ?><td><a href='<?=baseurl'/streamfeed.html' )?>'>
      <img src='<?=$rssiconurl?>'> /streamfeed.html</a></td><? ?>  
      <td align='left'>browser-friendly HTML</td>
    </tr></table>

<p>Other syndication formats will be available in the future. </p>

<p>StreamFeed URLs are usable in any news reader that supports HTTP authentication. Simply copy the URLs on the links listed above, and enter them into your news reader. Keep them private, as they grant access to private information. Please note that without the authentication credentials (appearing before the '@' in the URL), the URLs will not work. One consequence of this is that you won't always be able to subscribe to the feeds directly in your browser, unless you add them explicitly. </p>

<p><a href='<?=baseurl'/streamfeed.phps'false )?>'>View StreamFeed's source code</a>.</p>

<p><a href='http://natowelch.livejournal.com/tag/streamfeed'>Follow StreamFeed development news here</a> <a href='http://natowelch.livejournal.com/data/rss?tag=streamfeed'><img src='<?=$rssiconurl?>'></a></p>

<p>StreamFeed logo is courtesy of <a href='http://artscum.net/'>David Baumgart</a></p>

  </td></tr></table></td></tr></table>
  
<? 
  
// hide the about block for js-capable browsers explicitly requesting html format
  
if ( preg_match'/\.html$/'$subpath )  ) { 
?>
<script><!-- 
  document.getElementById('about').style.display = 'none' ; 
  --></script>
<? ?>
<script><!--
  // don't force js-capable browsers to reload the page for about info. 
  document.getElementById('aboutlink').href = '#aboutstreamfeed' ; 
  --></script>  

<p>
<? 

  
if ( ! is_array$stream['posts'] ) ) { 
    
?><a href='<?=$loginurl?>'>Get your Facebook StreamFeed</a><?
  
} else {
    foreach ( 
$stream['posts'] as $postid => $post ) {
      if ( ! 
preg_match'#facebook.com/apps/application.php#'
        
$stream['profiles'][$post['actor_id']]['url'] ) 
        ) {
        
renderposthtml$postid ) ; 
      }
    } 
  }
  
?>
</p>

<pre><?
#var_dump( $stream ) ;
?></pre>

</body></html>
<?
  
}

function 
renderposthtml $postid ) {
  
extract$GLOBALS ) ;
  
$post $stream['posts'][$postid] ;
  if ( ! 
is_array$stream )
    or ! 
is_array$post 
    ) {
    return 
false ; }
    
?>  

<table border=0 class='streamfeedpost' width=600 >
  <tr><td valign='top' width=50>
    <a href='<?=$stream['profiles'][$post['actor_id']]['url']?>'>
      <img src='<?=$stream['profiles'][$post['actor_id']]['pic_square']?>'></a>
      <?=( ! in_array$post['type'], $posttypes) ? "<br/>{$post['type']}=?" '' ) ; ?>
    </td>
    
  <td valign='top' width='100%'>
    
    <a href='<?=$stream['profiles'][$post['actor_id']]['url']?>'>
      <?=$stream['profiles'][$post['actor_id']]['name']?></a> 
      
      <? if ( strlen$post['target_id'] ) ) { ?>
        &gt; <a href='<?=$stream['profiles'][$post['target_id']]['url']?>'>
          <?=$stream['profiles'][$post['target_id']]['name']?></a>
          
          <a href='<?=$stream['profiles'][$post['target_id']]['url']?>'>
          <img align='right' src='<?=
        $stream
['profiles'][$post['target_id']]['pic_square']?>'></a>
      <? ?>
      
    <?=htmlspecialchars$post['message'] )?><br/>


    <? # Attachments
    
if ( is_array$post['attachment'] ) ) { ?>
      <table class='streamfeedattachment'><tr><td> 
      
        <table align='left'><tr><td>
          <? if ( is_array$post['attachment']['media'] ) ) {
          foreach ( 
$post['attachment']['media'] as $mid => $medium ) { 
            if ( 
strlen$medium['href'] ) and strlen$medium['src'] ) ) {
              
?><a target='_blank' href='<?=$medium['href']?>'>
                <img src='<?=$medium['src']?>'></a><? 
                
            elseif ( 
strlen$medium['music']['source_url'] ) 
              and 
strlen$medium['music']['title'] ) ) { 
              
?><a target='_blank' href='<?=$medium['music']['source_url']?>'>
                <?=$medium['music']['title']?></a><? ?> 
                
              <? if ( ! in_array$medium['type'], $mediatypes ) ) {  
                echo 
"<br/>{$medium['type']}=?" ; } ?>
              <br/>
              
          <? } } ?></td></tr></table>
          
        <? if ( strlen$post['attachment']['href'] ) 
          and ( 
$post['attachment']['href'] != 'http://www.facebook.com/' )
          and 
strlen$post['attachment']['name'] ) ) { ?>
          <a target='_blank' href='<?=$post['attachment']['href']?>'><?=
            $post
['attachment']['name']?></a><br/><? ?>
        <?= $bit $post['attachment']['caption'] ) ? $bit .'<br/>' '' ?>
        <?= $bit $post['attachment']['description'] ) ? $bit .'<br/>' '' ?>
        <? if ( is_array$props $post['attachment']['properties'] ) ) {
        foreach( 
$props as $prop ) { ?>
          <strong><?=$prop['name']?>:</strong> 
            <?=$prop['text']?><br/>
        <? } } ?>
        
        </td></tr></table>
        
    <? ?>


    <? # date & permalink ?>
    <? if ( $plink strlen$post['permalink'] ) ) { ?>
    <a target='_blank' href='<?=$post['permalink']?>'><? ?>
      <?=date'Y/m/d h:i:sa'$post['created_time'] )?><?=$plink '</a>' '' ?>
      <?= ( ( $post['updated_time'] +
        - ( 
$post['created_time'] +) ) 
        ? 
' updated '
        
date'Y/m/d h:i:sa'$post['updated_time'] ) : '' ?>
      <br/>


    <? # Comments
    
?><table border=0 width='100%' class='streamfeedcomments'><?
    
if ( count$post['comments']['comment_list'] ) < $post['comments']['count'] ) {
      
?><tr><td></td><td>
        <? if ( $plink ) {?><a target='_blank' href='<?=$post['permalink']?>'><? ?>
              <?=$post['comments']['count']?> comments
              <?= $plink '</a>' '' ?>
              </td></tr><? 
    
}
    
    if ( 
is_array$post['comments']['comment_list'] ) ) {
    foreach( 
$post['comments']['comment_list'] as $comment ) {
      
?>
      <tr class='streamfeedcomment'><td valign='top' width='50'>
          <a href='<?=$stream['profiles'][$comment['fromid']]['url']?>'>
            <img src='<?=$stream['profiles'][$comment['fromid']]['pic_square']?>'></a>
          </td>
        <td valign='top'>
          <a href='<?=$stream['profiles'][$comment['fromid']]['url']?>'>
            <?=$stream['profiles'][$comment['fromid']]['name']?></a> 
          <?=htmlspecialchars$comment['text'] )?><br/>
          <?=date'Y/m/d h:i:sa'$comment['time'] )?><br/>
          </td>
        </tr>
        <? } } ?>
        
        <? if ( $plink and $post['comments']['can_post'] ) { ?>
        <tr><td width='50'></td><td>
          <a target='_blank' href='<?=$post['permalink']?>'>
            Comment</a></td></tr>
        <? ?>
        </table>

    </td>
    
  </tr>
  <tr><td> &nbsp; </td></tr>
  </table>

<?
  
return true ;
  
}


## GATHER info

$stream false ;
$iscred false ;

// subpath (the part of the URI between the scriptname and the query string)
$subpath str_replace"?{$_SERVER['QUERY_STRING']}"''
  ( ( 
$subpath preg_replace"@^{$_SERVER['SCRIPT_NAME']}@"''
    
$_SERVER['REQUEST_URI'] ) ) == $_SERVER['REQUEST_URI'] ) 
    ? 
'' $subpath ) ;

// handle authentication if requested
// this prevents browsers from complaining 
// that you're logging into a site that doesn't require authentication
bugdump'php_auth_digest'$_SERVER['PHP_AUTH_DIGEST'] ) ;
if ( ( 
strlen$_REQUEST['auth'] ) or preg_match'@/auth@'$subpath ) )
  and ( ! 
strlen$_SERVER['PHP_AUTH_USER'] ) 
    or ! 
strlen$_SERVER['PHP_AUTH_PW'] ) 
    
#or ! strlen( $_SERVER['PHP_AUTH_DIGEST'] )
    
)
  ) {
  
header"HTTP/1.0 401 Unauthorized" ) ; 
  
header('WWW-Authenticate: Basic realm="Facebook StreamFeed"');
  
renderstreamhtml() ;
  exit ;
}  

// print out source code on request
if ( preg_match'/\.phps$/'$subpath ) ) {
  
highlight_file$_SERVER['SCRIPT_FILENAME'] ) ; 
  exit ; 
}

// extracts $apikey and $appsecret
extract( require( 'streamfeedconfig.inc.php' ) ) ;

$loginurl "http://www.facebook.com/login.php"
  
."?api_key={$apikey}&v=1.0&next="
  
."http". ( $_SERVER['HTTPS'] == 'on' 's' '' ) ."%3A%2F%2F"
  
$_SERVER['HTTP_HOST']
  . 
urlencode$_SERVER['SCRIPT_NAME'
    . 
$subpath
    
.( $debug "?debug=1" '' ) )
  ;

require_once 
'facebook-platform/php/facebook.php';
require_once 
'facebook-platform/php/facebook_desktop.php';

// clear fb cookie entries ; I don't want the facebook object to have them. 
foreach ( $_COOKIE as $key => $val ) {
  if ( 
preg_match"/^{$apikey}/"$key ) ) {
    unset( 
$_COOKIE[$key] ) ; } }
// If  we've been passed an auth_token, save it elsewhere and clear it
// We will fetch the session ourselves later.
if ( strlen$_GET['auth_token'] ) ) {
  
$auth_token $_GET['auth_token'] ; 
  unset( 
$_GET['auth_token'] ) ;
}

$fb = new FacebookDesktop($apikey$appsecret);

#bugdump( $fb ) ;

#$AUTH['fbsk'] = $_SERVER['PHP_AUTH_USER'] ;
#$AUTH['fbss'] = $_SERVER['PHP_AUTH_PW'] ;

// if there's an auth_token, cash it in and get session details from that
if ( strlen$auth_token ) ) { 
  
#bugdump( 'getting session from auth_token' ) ;
  
$atsession $fb->do_get_session$auth_token ) ;
  
$atsession['fbsk'] = $atsession['session_key'] ;
  
$atsession['fbss'] = $atsession['secret'] ;
}

// if there is an encrypted feedid parameter, fetch session details from it
if ( strlen$_REQUEST['feedid'] ) ) {

  
$feedid split':'
    
gzuncompressmcrypt_ecbMCRYPT_TWOFISH$appsecret
      
base64_decode$_SERVER['PHP_AUTH_PW'] . $_REQUEST['feedid'] ) , MCRYPT_DECRYPT
      
mcrypt_create_ivmcrypt_get_iv_sizeMCRYPT_TWOFISHMCRYPT_MODE_ECB ) )  
      ) ) 
    ) ;
  
  
# todo: handle failures?
  
  
$feedid = array( 
    
'fbsk' => array_shift$feedid ), 
    
'fbss' => array_shift$feedid 
    ) ;
}

bugdump'INPUTS:''cookie',$_COOKIE'session',$_SESSION
  
'post',$_POST'get',$_GET'httpauth',$AUTH 'auth_token session',$atsession ,
  
'feedid session'$feedid  
  
) ;

// later non-blank sources take precedence over earlier ones
$fbsksource $fbsssource false ;
foreach ( array( 
'fbsk''fbss' ) as $var ) {
foreach( array( 
'_COOKIE''_SESSION'
  
'_POST''_GET''atsession''feedid' ) as $sourcename 
  
) { 
  
$source = $$sourcename ;
  if ( 
strlen$val $source[$var] ) ) {
    $
$var $val ;
    
$GLOBALS[$var.'source'] = $sourcename ;
  }
  
#bugdump( 'scanloop', $sourcename, $var, $source, $$var ) ;
} }

bugdump'fbsk'$fbsk$fbsksource
  
'fbss'$fbss$fbsssource 
  
'auth_token'$auth_token ,
  
'subpath'$subpath,
  
'loginurl'$loginurl,
  
'baseurl'baseurl(),
  
'apikey'$apikey
  
'strlen( $appsecret )'strlen$appsecret )
  ) ;


## VERIFY session info

// set found paramters
if ( strlen$fbsk ) and strlen$fbss 
  
#and strlen( $auth_token ) 
  
)  {

  
// set found parameters
  
$fb->api_client->session_key $fbsk ;
  
$fb->secret $fbss ;
  
$fb->api_client->use_session_secret$fbss ) ;
  
  
#bugdump( $fb ) ;
  
} else { 
  
// bounce people who've offered no credentials and are requesting a sub-url
  
if ( strlen$subpath ) ) {
    
$baseurl baseurl() ;
    
bugdump'Location: baseurl '"<a href='{$baseurl}'>{$baseurl}</a>" ) ;
    if ( ! 
$debug ) { header'Location: '$baseurl ) ; }
    exit ;
  }
  
renderstreamhtml() ;
  exit ;
}

// check for and prompt for permissions
// this also verifies the provided session key and secret
try {
  if ( ! 
$fb->api_client->users_hasAppPermission('read_stream')
    or ! 
$fb->api_client->users_hasAppPermission('offline_access')
    ) {
    
// off you go to grant our app permission to read your stream 'offline'
    
$getpermsurl "http://www.facebook.com/connect/prompt_permissions.php"
      
."?api_key={$apikey}&fbconnect=true&v=1.0&display=popup&extern=1"
      
."&next="
      
."http". ( $_SERVER['HTTPS'] == 'on' 's' '' ) ."%3A%2F%2F"
      
$_SERVER['HTTP_HOST']
      . 
urlencode$_SERVER['SCRIPT_NAME'
        .( 
$debug "?debug=1" '' ) )
      
      .
"&ext_perm=read_stream,offline_access"
      
;
    
bugdump'getpermsurl',  
      
"<a href='{$getpermsurl}'>{$getpermsurl}</a>" ) ;
    
    if ( ! 
$debug ) { header'Location: '$getpermsurl ) ; }
    exit ;
  }
  
} catch ( 
FacebookRestClientException $oops ) {
  if ( 
$oops->getCode() == 102 ) {
    
// session is invalid or expired. 
    // off you go to get a new auth token
    
bugdump'Location:'"<a href='{$loginurl}'>{$loginurl}</a>" ) ;
    if ( ! 
$debug ) { header'Location: '$loginurl ) ; }
    exit ;
   
  } else {
    
// something else is wrong. re-throw the exception
    
throw $oops ;
  }
}

// our credentials are now verified
if ( $iscred = ( strlen$fbsk ) and strlen$fbss ) ) ) {

  
// generate a feedid string for use in URLs
  
$feedid urlencode$_REQUEST['feedid'] = 
    
base64_encodemcrypt_ecbMCRYPT_TWOFISH$appsecret
      
gzcompress"{$fbsk}:{$fbss}" ) , MCRYPT_ENCRYPT 
      
mcrypt_create_ivmcrypt_get_iv_sizeMCRYPT_TWOFISHMCRYPT_MODE_ECB ) )  
      ) ) ) ;
      
}


// uid should be tacked onto the end of the session key
$fbsks split'-'$fbsk ) ;
if ( 
count$fbsks ) > ) {
  
$uid array_pop$fbsks ) ;
  
$userinfo $fb->api_client->users_getInfo$uid'name' ) ;
  if ( 
count$userinfo ) ) {
    
$userinfo array_pop$userinfo ) ;
    
extract$userinfo ) ;
  }
}


// bounce people arriving via auth_token to the real, bookmarkable streamfeed URL
if  ( strlen$_REQUEST['auth_token'] ) ) {
    
$baseurl baseurl() ;
    
bugdump'Location: baseurl '"<a href='{$baseurl}'>{$baseurl}</a>" ) ;
    if ( ! 
$debug ) { header'Location: '$baseurl ) ; }
    exit ;
}

## FETCH information


// fetch the user's stream
# suppress response bugdump() for the stream.get call
#$suppressresponse = true ;
$stream $fb->api_client->stream_get() ;
$suppressresponse false ;

// TODO: fetch recent activity/notifications for slip-streaming. 
$notifications $fb->api_client->call_method
  
'facebook.notifications.getList', array( 'include_read' => '1' ) ) ;
#bugdump( 'notifications', $notifications ) ;

// re-index $stream array according to appropriate indices
foreach ( array( 'post_id' => 'posts''id' => 'profiles' 
  as 
$key => $array 
  
) {
  
$newarray = array() ;
  if ( 
is_array$stream[$array] ) ) {
    foreach ( 
$stream[$array] as $item ) {
      
$newarray[$item[$key]] = $item ; }
    
$stream[$array] = $newarray ;
  }
}




## RENDER the stream

// escape html if live, otherwise render it direct if debugging
function hschtml $input ) {
  return ( 
$GLOBALS['debug'] ? $input htmlspecialchars$input ) ) ; }

// escape xml if debugging, otherwise render it direct
function hscxml $input ) {
  
#bugdump( 'hscxml', $input, htmlspecialchars($input) ) ;
  
return ( $GLOBALS['debug'] ? htmlspecialchars$input ) ."</pre>" $input ) ; 
}
function 
hsc$input ) { return htmlspecialchars$input ) ; }


# RSS 2.0 format
if ( preg_match'/rss2.xml$/'$subpath ) ) {

  
#>
  
// send rss content type header unless debugging
if ( $debug ) { ?><pre><? 
else { 
header"Content-Type: application/rss+xml" ) ; } 

ob_start() ;
?>
<<??>?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
      xmlns:media="http://search.yahoo.com/mrss/"
      xmlns:dc="http://purl.org/dc/elements/1.1/"
    >
  <channel>
    <title><?=$name?>&apos;s Facebook StreamFeed</title>
    <link><?= hscbaseurl'/streamfeed.html' ) ) ; ?></link>
    <description><?=$name?>&apos;s Facebook StreamFeed</description>
    <language>en-us</language>
    <generator>Facebook StreamFeed</generator><docs>http://www.rssboard.org/rss-specification</docs>
    <lastBuildDate></lastBuildDate>
    <webMaster>nato@streamfeed.r30.net</webMaster>



<? if ( is_array$stream['posts'] ) ) { ?>
<? 
foreach ( $stream['posts'] as $postid => $post ) {
  if ( ! 
preg_match'#facebook.com/apps/application.php#'
    
$stream['profiles'][$post['actor_id']]['url'] ) 
    ) {  
?>
    
<? #if ( ++$idx > 50 ) { continue ; } ?>
<? 
if ( ! in_array$post['type'], $posttypes ) ) { ?>

<item>
      <guid isPermaLink="false"><?=$postid?></guid>
      <title><?=hsc"{$postid} Unknown post type {$post['type']}" )?></title>
      <link><?=hsc"http://planetdone.com/not.php/{$postid}" ?></link>
      <description><?
        ob_start
(); 
        
?><pre><?
        var_dump
"Unknown Post type {$post['type']}"$post ) ; 
        
?></pre><?
        
echo hschtmlob_get_clean() ) ;
        
?></description>
    </item>
    
<? ?>

    <item>
      <? if ( $plink strlen$post['permalink'] ) ) { ?>
        <guid isPermaLink="true"><?=hsc$post['permalink'] )?></guid>
      <? } else { ?>
        <guid isPermaLink="false"><?=hsc$postid )?></guid>
      <? ?>
      <title><? ob_start() ; 
      echo 
$stream['profiles'][$post['actor_id']]['name'] ;
      if ( 
strlen$post['target_id'] ) ) { 
        echo 
" > "
          
$stream['profiles'][$post['target_id']]['name'
          .
":" 
      }
      echo 
' 'substr$post['message'] , 0200 
        . ( ( 
strlen$post['message'] ) > 200 ) ? '...' '' )
        ;    
      echo 
hscob_get_clean() ) ;
      
?></title>
      <link><?=hsc$post['permalink'] )?></link>
      <pubDate><?=hscdate'Y/m/d h:i:sa'$post['created_time'] ) ) ?></pubDate>
      <description>
      
      
<?= hscxmlob_get_clean() ) ?>
<? 
  
if ( $debug ) { ?></pre><? }
  
ob_start() ; 
  
renderposthtml$postid ) ;
  echo 
hschtmlob_get_clean() ) ; 
  if ( 
$debug ) { ?><pre><? 
  
ob_start() ; 
  
?>


</description>
    </item>
    
<? } } } ?>

<? if ( count$bugout ) ) { ?>
    <item>
      <guid isPermaLink="false">bugout</guid>
      <title>BugOut:</title>
      <link></link>
      <description>
        <? ob_start() ;
        
var_dump$bugout ) ; 
        echo 
hschtml'<pre>'ob_get_clean() ) ;
        
?>
      </description>
    </item>
<? ?>

  </channel>
</rss>

<?=hscxmlob_get_clean() ) ?>
<? 

  
#<
  
# An HTML page with about and help text is the default format
} else {
  
renderstreamhtml() ;
}


?>
<? 
/* LICENSE:
Copyright (c) 2009 Nato Welch
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
    * Neither the name of the <ORGANIZATION> nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ 
?>