Connecting Twitter (or StatusNet) to iChat (or Adium) with Python

Sharing status updates with IM buddies

Jun 27, 10:59 AM

This is just a simple Python script I wrote to update my IM status with the IM programs on my Mac. (On my Windows and Linux machines, there’s a Pidgin plugin that takes care of this for me.)

The best way to run this is from cron something like this in your crontab will poll Twitter and update Adium/iChat every 15 minutes:

0,15,30,45 * * * * python /Users/adam/Library/Scripts/twit.py > /dev/null
# Put your Twitter screen name here.
twit = 'daydreamlab'

from appscript import *
from xml.dom.minidom import parse
from urllib2 import urlopen

#Grab the XML feed from the Twitter API.
tweetXML = urlopen('http://twitter.com/statuses/user_timeline/'+twit+'.xml?count=1')

# If you use identi.ca, use this instead:
#tweetXML = urlopen('http://identi.ca/api/statuses/user_timeline/'+twit+'.xml?count=1')

#Grab the text of the newest entry.
newestTweet = parse(tweetXML).getElementsByTagName('text').item(0).firstChild.data

#Tell iChat to update its status message. (Comment this line out if you don't use iChat.)
app('iChat').status_message.set(newestTweet)

#Tell Adium to update its status message for each account. 
#(Uncomment these lines if you use Adium.)
#for account in app('Adium').account.get():
#        account.status_message.set(newestTweet)

atb_editarea

Add a useful code editor to Textpattern's Presentation tabs

Jun 15, 04:29 PM

The atb_editarea plugin simply takes Christophe Dolivet’s EditArea, a free javascript editor for source code, and uses it to enhance the textareas inside your Textpattern admin area’s Presentation tab.

You can also follow this on GitHub.

Download: atb_editarea-0.9.zip

atb_if_form

Conditional tags for Textpattern forms.

May 31, 01:48 PM

Lately, I’ve been writing Textpattern pages for relatively complex sites that do things like this:

<txp:if_section name="widgets,sprockets,thingies">
    <txp:hide>The widgets, sprockets, and thingies sections have their own special layouts.</txp:hide>
    <txp:output_form form='content-<txp:section />' />
<txp:else />
    <txp:hide>All the other sections look pretty much the same.</txp:hide>
    <txp:output_form form='content-default' />
</txp:if_section>

This plugin is designed to simplify this somewhat, and eliminate that list of section names (which is just one more thing for me to forget to update when I change the site).

atb_if_form

A simple, hopefully self-explanatory conditional form:

<txp:atb_if_form name="someform">
	<p>someform exists.</p>
<txp:else />
	<p>someform doesn't exist</p>
</txp:atb_if_form>

atb_output_form_if_exists

Can be used exactly the same way as the built-in output_form tag, except it doesn’t raise an error if the form provided doesn’t exist:

<txp:atb_output_form_if_exists form='foo-<txp:section />'>
    This text will be available via the txp:yield tag in foo-[section].
</txp:atb_output_form_if_exists>

It can also be used with a txp:else tag:

<txp:atb_output_form_if_exists form='foo-<txp:section />'>
    This text will be available via the txp:yield tag in foo-[section].
<txp:else />
    <txp:output_form form="foo-default" />
</txp:atb_output_form_if_exists>

You can also follow this on GitHub.

Download: atb_if_form-0.9.txt

Parsing the Twitter API's XML with XSLT

Another Symphony CMS Utility

May 1, 03:14 PM

This is just a simple Twitter XML to XHTML parser I wrote for a site I’m developing with Symphony CMS.
Note that it relies on the date format converter I posted earlier.

There’s likely a much more efficient way to do what I’m doing here, but as I’m relatively new to XSLT, I haven’t found it yet.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
	xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
	xmlns:str="http://exslt.org/strings" 
	xmlns:date="http://exslt.org/dates-and-times"
	extension-element-prefixes="str">

<xsl:import href="../utilities/rfc-to-iso-dates.xsl" />

<xsl:template match="//statuses/status">
    <li class="tweet">
    <!--make date display prettier-->
    <xsl:variable name="now" select="date:date-time()" />

    <xsl:variable name="tweetdate">
        <xsl:call-template name="rfc-to-iso">
        <xsl:with-param name="input-date" select="created_at" />
        </xsl:call-template>
    </xsl:variable>

    <xsl:variable name="datediff" select="date:difference($tweetdate, $now)" />
    <xsl:variable name="secsdiff" select="date:seconds($datediff)" />

    <span class="date">
        <xsl:choose>
            <xsl:when test="$secsdiff < 60">
            <xsl:value-of select="$secsdiff"/> seconds ago</xsl:when>

            <xsl:when test="$secsdiff < 3600">
            <xsl:value-of select="round($secsdiff div 60)" /> minutes ago</xsl:when>

            <xsl:when test="$secsdiff < 86400">
            about <xsl:value-of select="round($secsdiff div 3600)" /> hours ago</xsl:when>

            <xsl:otherwise>
            <xsl:value-of select="concat(date:day-in-month($tweetdate), '. ', date:month-name($tweetdate))" /></xsl:otherwise>
        </xsl:choose>:
    </span>

    <xsl:text> </xsl:text>

    <!-- Is this a reply? Add an "(in reply to whoever)" link. -->
    <xsl:if test="in_reply_to_status_id != ''">
        <a class="responselink" href="http://twitter.com/{in_reply_to_screen_name}/status/{in_reply_to_status_id}">(in reply to <xsl:value-of select="in_reply_to_screen_name" />)</a>
        <xsl:text> </xsl:text>
    </xsl:if>

    <!-- Parse the text word-by-word -->
    <xsl:for-each select="str:tokenize(normalize-space(text), ' ')">

        <xsl:choose>

        <!-- @reference. Make this a link to the referenced user's Twitter page. -->
        <xsl:when test="starts-with(., '@')">
            <a>
                <xsl:attribute name="href">
                <xsl:value-of select="concat('http://twitter.com/', substring(.,2))" />
                </xsl:attribute>
                <xsl:value-of select="." />
            </a> 
        </xsl:when>

        <!-- Hashtag. Link to a Twitter search for this tag. -->
        <xsl:when test="starts-with(., '#')">
            <a>
                <xsl:attribute name="href"><xsl:value-of select="concat('http://search.twitter.com/search?result_type=recent&q=%23', substring(., 2))" />
                </xsl:attribute>
                <xsl:value-of select="." />
            </a> 
        </xsl:when>

        <!-- Web URL. Turn it into a link. -->
        <xsl:when test="starts-with(., 'http:')">
            <a href="{.}"><xsl:value-of select="." /></a> 
        </xsl:when>

        <!-- Just a word. -->
        <xsl:otherwise>
            <xsl:value-of select="." /> 
        </xsl:otherwise>

        </xsl:choose>

        <xsl:text> </xsl:text>

    </xsl:for-each>

    </li>

</xsl:template>

</xsl:stylesheet>

Download: display-twitter.xsl

atb_twitter

Display your formatted Twitter feed on your Textpattern site

Apr 18, 09:02 PM

atb_twitter displays feeds from microblogging platforms (Twitter and sites based on StatusNet, including identi.ca) on your website, using a Textpattern form for formatting, so you can do something like this:

<txp:atb_twitter count="20" user="daydreamlab">

<txp:atb_tw_if_first_tweet>
<h2><a href="http://twitter.com/daydreamlab"><txp:atb_tw_user_name /> on Twitter</a></h2>
<ul>
</txp:atb_tw_if_first_tweet>

<li>
<span class="tweetdate"><txp:atb_tw_created_at />:</span> 
<br /><txp:atb_tw_text />

<txp:atb_tw_if_is_reply>
<br />
<a class="inreply" href="http://twitter.com/<txp:atb_tw_in_reply_to_user_id />/status/<txp:atb_tw_in_reply_to_status_id />">
in reply to <txp:atb_tw_in_reply_to_user_id />
</a>
</txp:atb_tw_if_is_reply>

</li>

<txp:atb_tw_if_last_tweet>
</ul>
</txp:atb_tw_if_last_tweet>

</txp:atb_twitter>

If you’re interested in that kind of thing, you can follow development on GitHub.

Download: atb_twitter.txt

Date format conversion in XSLT

Translating RFC 822 dates to ISO 8601

Apr 18, 01:06 PM

This is a utility I wrote for a new site I’m developing with Symphony
It’s useful for translating dates from RSS feeds into ISO date values usable by the EXSLT date functions.

Note that this relies on the EXSLT str:tokenize() function.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
	xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
	xmlns:str="http://exslt.org/strings">

<xsl:template name="rfc-to-iso">
<xsl:param name="input-date" />

<!-- Read parts of the date into variables, reading backwards from the end,
     so we don't have to worry about the optional weekday -->
<xsl:variable name="year" select="str:tokenize($input-date, ' ')[last()]" />
<xsl:variable name="tz" select="str:tokenize($input-date, ' ')[last()-1]" />
<xsl:variable name="time" select="str:tokenize($input-date, ' ')[last()-2]" />
<xsl:variable name="day" select="str:tokenize($input-date, ' ')[last()-3]" />
<xsl:variable name="month" select="str:tokenize($input-date, ' ')[last()-4]" />

<xsl:value-of select="$year" />-<xsl:choose>
<!-- Translate month into a two-digit number -->
<xsl:when test="$month = 'Jan'">01</xsl:when>
<xsl:when test="$month = 'Feb'">02</xsl:when>
<xsl:when test="$month = 'Mar'">03</xsl:when>
<xsl:when test="$month = 'Apr'">04</xsl:when>
<xsl:when test="$month = 'May'">05</xsl:when>
<xsl:when test="$month = 'Jun'">06</xsl:when>
<xsl:when test="$month = 'Jul'">07</xsl:when>
<xsl:when test="$month = 'Aug'">08</xsl:when>
<xsl:when test="$month = 'Sep'">09</xsl:when>
<xsl:when test="$month = 'Oct'">10</xsl:when>
<xsl:when test="$month = 'Nov'">11</xsl:when>
<xsl:when test="$month = 'Dec'">12</xsl:when>
</xsl:choose>-<xsl:value-of select="$day" />T<xsl:value-of select="$time" /><xsl:if test="$tz = '+0000'">Z</xsl:if><xsl:if test="$tz != '+0000'"><xsl:value-of select="$tz" /></xsl:if>

</xsl:template>

</xsl:stylesheet>

Download: rfc-to-iso-dates.xsl

atb_customize_feeds

Enhance your feeds

Oct 9, 07:58 AM

atb_customize_feeds allows you to modify Textpattern’s article syndication feeds by creating a few new forms that are either parsed and appended to your feeds, or which replace article bodies and excerpts in feeds.

Possible uses include appending file enclosures to your feeds (for podcasts, for example), generating excerpts with <a href="http://www.wilshireone.com/textpattern-plugins/rss_auto_excerpt">rss_auto_excerpt</a>, or appending permlinks to your articles’ text.

(Written and tested on Textpattern 4.2.0. Everything should work in any version since 4.0.4, but I make no promises…)

Download: atb_customize_feeds_v0.9.txt

Textpattern database functions

Some rare documentation for plugin authors

Apr 24, 06:20 PM

For my own reference, I went through Textpattern’s database library and made a list of the functions with a short description for each. I suspect this could be useful for others who need to extend Textpattern.

This is every function in txplib_db.php, as of version 4.0.8:


safe_pfx($table):
Adds the table prefix defined for this instance of Textpattern to $table and wraps the table name in backticks.

safe_pfx_j($table):
Does the same as safe_pfx(), but for a comma-separated list of tables.

safe_query($q='',$debug='',$unbuf=''):
Run a query and return the result handle or false. If $debug is true, the query will be logged. safe_query() will warn if the query fails and the current page is the admin interface or the site is currently in “debug” or “testing” mode. If $unbuf is true, use mysql_unbuffered_query() to get the result, otherwise use mysql_query(). safe_query() seems to be mainly intended as a resource for the other functions in this library, rather than as something to be called directly—(nearly) all of the functions below call safe_query, and the $debug argument is always just passed to it.

safe_delete($table, $where, $debug=''):
Deletes a row or rows from $table where $where.

safe_update($table, $set, $where, $debug=''):
"UPDATE $table SET $set WHERE $where".

safe_insert($table,$set,$debug=''):
Again using safe_query(), inserts a row into $table using data $set.

safe_upsert($table,$set,$where,$debug=''):
Tries to update a table row with the arguments provided, if that fails it will insert a new row.

safe_alter($table, $alter, $debug=''):
ALTERs $table. Use this to modify an existing table in the database (adding a new field, for example).

safe_optimize($table, $debug=''):
OPTIMIZEs $table. Probably not useful for plugin authors.

safe_repair($table, $debug=''):
REPAIRs $table. Again, probably (hopefully!) not useful for plugin authors.

safe_field($thing, $table, $where, $debug=''):
Runs the query provided (”SELECT $thing FROM $table WHERE $where“) and returns the first cell of the first result row.

safe_column($thing, $table, $where, $debug=''):
Returns a (number-indexed) array containing the first field from the result of the query given. If $thing includes more than one field, all of them after the first are ignored.

safe_row($things, $table, $where, $debug=''):
Returns an associative array of the first row of the results from the query generated from the arguments.

safe_rows($things, $table, $where, $debug=''):
Returns an array of associative arrays containing the complete results of the query generated from the arguments.

safe_rows_start($things, $table, $where, $debug=''):
Returns a query resource handle, which can be used to read data directly or passed through nextRow() (below), which will automatically run mysql_free_result() after the last row is returned.

safe_count($table, $where, $debug=''):
Returns the number of rows in $table matching the query $where.

safe_show($thing, $table, $debug=''):
Runs a MySQL SHOW query. Note that the query takes the format of "SHOW $thing FROM $table"—this will only work for a few possible values of $thing.

fetch($col,$table,$key,$val,$debug=''):
Returns the first row from a query: "SELECT $col FROM $table WHERE $key = $val". Very much like safe_row, except it builds the (simple) SQL WHERE clause for you.

getRow($query,$debug=''):
Yet another way to get the first row of a result set. Used as the back end of safe_row()

getRows($query,$debug=''):
Returns a query result as an array.

startRows($query,$debug=''):
Returns a MySQL result resource. Called by safe_rows_start() to do its thing.

nextRow($r):
$r is a result resource. This function returns the next row, or, if we’re at the end of the result set, false. It also frees the memory used by $r when it reaches the end.

numRows($r):
Just returns the number of rows in the query.

getThing($query,$debug=''):
Like safe_field(), returns the first cell of the first result row.

getThings($query,$debug=''):
Returns the first column of results of $query in a number-indexed array.

getCount($table,$where,$debug=''):
Returns a row count of the query.

getTree($root, $type, $where='1=1', $tbl='txp_category'):
Returns all of the children of category $root (if you want the entire hierarchy tree, set this to 'root'). $type is one of ‘file’, ‘image’, ‘link’, or ‘article’. The returned array includes (for each category):
  • 'id': The category’s primary key (an auto-increment);
  • 'name': The category’s url-friendly name;
  • 'title': The human-readable name;
  • 'level': The number of steps between this category and $root;
  • 'children': The number of children this category has; and
  • 'parent': The category’s parent.

getTreePath($target, $type, $tbl='txp_category'):
Gets all the parents of category $target. The return value is the same as for getTree(), except that the 'parent' field isn’t included.

rebuild_tree($parent, $left, $type, $tbl='txp_category') and rebuild_tree_full($type, $tbl='txp_category'):
Rebuild the category hierarchy tree. Use rebuild_tree_full() instead of rebuild_tree() to rebuild an entire tree from category 'root'

get_prefs():
Retrieves a list of preferences from the database. Shouldn’t be necessary for plugin writers—you can read the $prefs global variable instead.

db_down():
Return a MySQL error message, ready to be sent to the browser.

XSLT: Formatting nested groups of items

Apr 17, 08:01 AM

I’m in the process of builidng my first site using the Symphony CMS, which uses XSLT as its template language.

Symphony uses XSLT as it’s template language. In theory, this should make it more flexible than core Textpattern, with its limited number of built-in tags (Although the huge number of plugins available for Textpattern definitely means Txp is still capable of holding its own). That is, if I actually knew XSLT well enough to write a single template turning to The Google dozens of times for answers to newbie questions.

For the site in question, I need to display a list of categories as a set of song lyrics, divided into verses of four lines each. In this first version, I’d decided I wanted the song divided into columns of four verses each. I’ve changed my mind on this design decision, but rather than let the headache experience go to waste, I’ve decided the code might interest other, still-newer-than-me newbies. It demonstrates how to build nested lists in XSLT, which is much less intuitive than in a procedural language like PHP (or Textpattern Tags with a plugin like zem_nth).

Your first impulse, if you’re used to PHP, is to do something like this to group blocks of items together:

<p>
<xsl:template match="data/posts">
	<xsl:if test="position() mod 4 = 1"></p><p></xsl:if>
	<xsl:apply-templates />
</xsl:template>
</p>

That so doesn’t work.

The problem is, it’s not valid XML—the parser will see an xsl:if tag matched with a closing p tag and scold you (Symphony won’t even let you save a template with this markup in it—it fails the first test the CMS subjects it to).

To handle nested groups, you have to do something like this (I’m using the Blueprint CSS framework, thus the divs with class "span-8"):

<xsl:template match="/data/categorylist">
	<!-- For every sixteenth item in the list: -->
	<xsl:for-each select="entry[position() mod 16 = 1]">

		<!--Process this item and the next 16, putting them inside a div -->
		<div class="lyrics span-8">
		<xsl:for-each select=".|following-sibling::entry[position() &lt; 16]">
			<!-- With every fourth item in this block of 16: -->
			<xsl:if test="position() mod 4 = 1">
			<!-- Wrap the current item and the next four in a p -->
			<p>
				<xsl:apply-templates select=".|following-sibling::entry[position() &lt; 4]"/>
			</p>
			</xsl:if>
		</xsl:for-each>
		</div>

	</xsl:for-each>
</xsl:template>

As the comments explain, you have to grab every nth item, then apply your templates to that item plus the following n-1, or, in XPath, .|following-sibling::entry[position() &lt; 16] (’.’ means the current item in the list being processed; ‘|’ means “and”).

Mixing GET variables and segment-based addressing in CodeIgniter

Feb 19, 09:15 AM

“Since CodeIgniter does not utilize GET strings, there is no reason to allow” GET variables, the CodeIgniter User Guide informs us.

Unfortunately, occasionally there is a reason to use those variables—in my case, an in-house authentication program interfered with POST requests made by Javascripts—and it’s not entirely obvious how to make them available in your CodeIgniter app. The Guide seems to suggest enabling query strings, but while that gives you access to the $_GET array, it also cripples your existing URLs—using “query strings” essentially means choosing controllers and methods to execute using GET variables exclusively, what I usually see called “messy URLs.”

The solution is (relatively) simple, but not immediately obvious. I found the solution in this forum post (hopefully it’ll be more visible here). Add these settings to your applications config.php:

$config['uri_protocol'] = "PATH_INFO"; 
$config['enable_query_strings'] = TRUE;
$config['permitted_uri_chars'] = 'a-z 0-9~%.:_\-?=+&;';

This should give you an app that uses URI segments to map requests to controllers, but can also pull an occasional argument out of the $_GET superglobal, using the more-or-less generic, universal .htaccess settings I “borrowed” from Textpattern:

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^(.*)$ index.php/$1 [L]
</IfModule>