Email-Notifications for Watchlist in DekiWiki

After i have installed DekiWiki for our internal purposes on eBesucher, i realized, that it do not support Email-Notification for the Watchlist. It has only the option to get the notifications as RSS-Feed. Well, ok, this might not be the problem. Why not? Thunderbird supports RSS-Feeds, so i set it up to get my Watchlist as RSS. But what i did not know: you have to be logged in, to view the watchlist. This means that only Firefox can read my RSS-Watchlist properly.

After that some using of google, i found an api-Link for the feed, that handles the authentication via standard-HTTPauth. This is something like this: http://yourwiki/@api/deki/site/feed?title=Special:Watchlist

So now i could add the Watchlist in my Thunderbird. But what’s this? The messages seemed broken: they only said that some page has been changed. But without the changes really to be included. At first, i thought, it was a Thunderbird-Problem. But after some testing, I found out, that the feed was only 6kb unstead of 13kb. Why that? I think there is a bug in the DekiWiki-Permissionsmangement, the API-HTTPauth does not give me the full view permissions. It is still necessary to pass the authorisation-cookie (authtoken) to get the full feed.

So to summ up, I can not use any RSS-Feed-Reader other than my browser. Firefox can view the feed properly, but there is no notification feeling. And rss2email doesn’t work with this too.

That’s why i’ve spent about a day on writing a php-script to forward the messages from this tricky feed to email properly. And i would like to share this. So let’s start!

Here is the Part that handles RSS and Email:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
<?php    
/**
 * DekiReader
 * is a class for getting instant email notifications with DekiWiki, if someone
 * changes a site in your Watchlist
 *
 * This script is free to use for private and commercial purposes
 * Author: Evgeny Anisiforov
 * mailtO: jeff at ebesucher point de
 *
 * I spend a whole day on writing this, so 
 * Please just leave a comment in my blog, if you use this. ;)
 */ 
 
/**
 * The most problem about getting notifications is that DekiWiki requires us
 * to be logged in (with a cookie) to be able to view the full rss feed
 * of changes (including changed things)
 * This is a big problem, as the most rss read does not have such function.
 * This script emulates a login for each user to get the auth cookie
 * and then sends the contents of the RSS-feed to the specified email-address
 * It also takes care of marking old messages as read, so your users won't
 * get notifications more than one time
 * 
 * The best practice would be to use a cronjob to start DekiReader every X minutes.
 * like this:
          * / 10 * * * * cd /www/Cron_Jobs/DekiReader; nice php notifyfromdb.php
 
 * notice: this file has only the class definitions, of cource you need a script
 * to create an object of DekiReader and pass the userdata to it.
 * */
 
/*
* this script needs the PEAR-packages "http_client" and "http_request"
*/
require_once "HTTP/Request.php";
require_once "HTTP/Client.php";
 
define('ERROR_DEKIWIKI_WRONG_CREDENTIALS', 'ERROR_DEKIWIKI_WRONG_CREDENTIALS');
define('ERROR_WEB_SERVICE', 'ERROR_WEB_SERVICE');
 
/* ------- Cookie's creation for Deki Wiki ------- */
class DekiCookie {
    public $url = "http://wiki.ebesucher.in";
 
    /** thanks to kurdte from: http://wiki.developer.mindtouch.com/MindTouch_Deki/Specs/Authentication_Providers */
    public function getAuthToken($login, $password) {
        /* Request a value for the user's cookie */
        $url = $this->url."/@api/deki/users/authenticate/";
        $req =& new HTTP_Request($url);
        $req->setBasicAuth($login, $password);
        $response = $req->sendRequest();
 
        if (!PEAR::isError($response)) {
        $output=$req->getResponseBody();
 
        /* Authentication proccess : if the response contains 401 authentication failed, the user is not registered */
        if(strpos($output,"authentication")==0 && strpos($output,"401")==0)
        {
            //$cookie_deki=$req->getResponseCookies();
            return $output;
            //setcookie("authtoken",str_replace("\"","",$cookie_deki[0]['value']),time()+3600, '/');
        }
        else
        {
            $error = ERROR_DEKIWIKI_WRONG_CREDENTIALS;
        }
        }
        else {
            $response->getMessage();
            $error = ERROR_WEB_SERVICE;
        } 
    }
}
 
class DekiReader {
    /**
        array with the date of users to check
        every item must contain: username, password
        every item may contain: authtoken
    */
    public $userdata;
    public $url;
    public $emaildomain;
    private $domain;
    /** Directory to store cache-Data (what feed-messages did we
      * allready read? */
    public $cachedir = "cache/";
 
    /**
     *  creates a DekiReader
     *  $emaildomain = the domain for sender-address
     *  if not given, we'll take the domain
    */
    public function DekiReader($domain, $userdata, $emaildomain = false) {
        $this->domain = $domain;
        $this->url = $domain."/index.php?title=Special:Watchlist&feed=rss";
        $this->userdata = $userdata;
        if (!$emaildomain) {
            //delete http:// or https:// at the beginning
            $d = eregi_replace('^http://', '', $domain);
            $d = eregi_replace('^https://', '', $d);
            $this->emaildomain = $d;   
        }
        else {
            $this->emaildomain = $emaildomain;
        }    
    }
 
    /**
    * Returns the proper RFC 822 formatted date. 
    * @return string
    */
    public function RFCDate($timestamp) {
       $tz = date("Z", $timestamp);
       $tzs = ($tz < 0) ? "-" : "+";
       $tz = abs($tz);
       $tz = ($tz/3600)*100 + ($tz%3600)/60;
       $result = sprintf("%s %s%04d", date("D, j M Y H:i:s", $timestamp), $tzs, $tz);
 
       return $result;
    }
 
    /** convert the Dateformat of the Feed to a Timestamp */
    public function strangeDate2Timestamp($date) {
        $regs = array();
        preg_match('~((\d+)-(\d+)-(\d+))T((\d+):(\d+):(\d+))Z~is', $date, $regs);
 
        //date and time
        $d = $regs[0];
        $t = $regs[1];
        return mktime($regs[6], $regs[7], $regs[8], $regs[3], $regs[4], $regs[2]);
    }
 
    /** read the message cache for a user */
    public function getCache($username) {
        //we use username and feed-url to cache read messages
        $cache  = $this->getCacheFilename($username);
 
        //read the cache
        $contents = file_exists($cache) ? file_get_contents($cache) : '';
 
        return $contents;
    }
 
    //get the filename of the message cache for a username
    public function getCacheFilename($username) {
        return $this->cachedir.'rss_' . md5($username."@".$this->url) . '.txt';
    }
 
    //add something to messagecache
    public function addToCache($username, $url) {
        $handle = fopen($this->getCacheFilename($username), 'a');
        fwrite($handle, $url . "\r\n");
        fclose($handle);
    }
 
    public function run() {
        $deki = new DekiCookie();
        header('Content-Type: text/plain');
        foreach ($this->userdata as $user) {
            echo "trying to get ".$this->url." for user ".$user['username']."\n";
            $req = new HTTP_Request($this->url);            
            $req->setMethod(HTTP_REQUEST_METHOD_GET);
            //do we have an authtoken?
            if (!$user['authtoken']) {
                $user['authtoken'] = $deki->getAuthToken($user['username'], $user['password']);
                echo "estimated authtoken for user ".$user['username'].": ".$user['authtoken']."\n";
            }
            $req->addCookie("authtoken", $user['authtoken']);
            $req->sendRequest();
            $rss = $req->getResponseBody();
 
            $xml    = new SimpleXMLElement($rss);
            $cache_contents = $this->getCache($user['username']);
 
            //some output
            echo 'Feed: ' . (string)$xml->link['href'] . "\r\n";
 
            //iterate the items in the feed
 
            foreach ($xml->entry as $item) {
                $url = (string)$item->link['href'];
                $id          = (string)$item->id;
                //did we allready read this?
                if (strpos($cache_contents, $id) !== false) {
                    echo "   read: $id\r\n";    
                    continue;
                }
                else {
                    echo "   unread: $id\r\n";
                }
 
                // The data from the feed
                $authorname  = (string)$item->author->name;
                //delete scopes for better compartibility
                $authorname  = str_replace("(", "", $authorname);
                $authorname  = str_replace(")", "", $authorname);
                //here we don't have a read possibility to get the email of
                //the author, we just simulate it by adding our domain name
                //to the username (or you could write a function that does that better..)
                $author      = '"'.$authorname.'"'."<".$authorname."@".$this->emaildomain.">";
 
                $title       = (string)$item->title;
                $link        = (string)$item->link['href'];
                $pubDate     = (string)$item->published;
                //convert the date from the feed to a compartible date format
                //for the emails, so that we can see the correct date of
                //the posting in our email program
                $updatedtimestamp = $this->strangeDate2Timestamp((string)$item->updated);
                $updated     = $this->RFCDate($updatedtimestamp);
                //we want all tags between <summary> and </summary>, so just
                //iterate through the children
                $description = "";
                foreach ($item->summary->children() as $child)
                {
                    $description .= $child->asXML();
                }
 
 
 
                $to   = $user['email']; 
                $mail = "
                <html><head></head><body>
                Page: <a href=\"$url\">$title</a>
                $description
                </body></html>"; 
 
                mail($to, '[Wiki] ' . $title, $mail, "From: $author"
                     ."\nContent-Type: text/html; charset=utf-8"
                     ."\nDate: ".$updated
                     ); 
 
                // we have send a notification for this item, so send
                // write this to the cache
                $this->addToCache($user['username'], $id);
            }
        }
    }
}
 
?>

So now lets use this! I use remote authentication on my site, so i’ve written a small file to read user data from my auth db.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
  //include some internal db stuff
 // ....
 
  //then
include_once("dekireader.php");
 
$watch_accounts = array("jeff", "sebastian");
if (count($watch_accounts)<1) die("Please define Accounts to watch!"); 
$sql = "SELECT Username as username, PW as password, Email as email FROM Admins WHERE Username IN ('".implode("','", $watch_accounts)."')";
$res = mysql_query($sql);
echo mysql_error();
$userdata = mysql_get_whole_result($res);
 
$dekireader =  new DekiReader('http://mywiki',
                              $userdata, 'myemaildomain.no');
 
$dekireader->run();        
 
?>

But you could also just select all all admins. Or hard-code userdata:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
  $userdata = array(array(
            'username'  => 'jeff',
            'password'  => 'secret',
            //'authtoken' => 'something', //you could cache authtokens to make the script faster
            'email'     => 'bla@bla.bla'
            )); 
$dekireader =  new DekiReader('http://mywiki',
                              $userdata, 'myemaildomain.no');
 
$dekireader->run();        
?>

I did not handle using internal dekiwiki database for authorizing local users. I also did not try this. But this should be no problem after you examine the db structure of dikiwiki. Let me know, if you try this!

So now just set up a cronjob to run this script regulary, and you have a good email notification for your wiki. Like this:
* / 10 * * * * cd /www/Cron_Jobs/DekiReader; nice php notifyfromdb.php

I’m still looking forward and hope that Deki-team will soon implement Deki to support this out-of-the-box.

You can use this script for any purpose, just please leave a comment! My blog is hungry ;)
Download the main script: dekireader.phps

4 Responses to “Email-Notifications for Watchlist in DekiWiki”


  1. 1 Roy

    That’s a great script - thanks for working on it ;) I can tell you that, yes, we *are* implementing email notifications in the next release of Deki. In fact, I was just working out the UI flows for this feature today with our development team! It’s something to look forward to soon.

  2. 2 Jeff

    This is nice to know, roy!
    I will wait for this feature. Dekiwiki is a great application, and it becomes better with every release.

  3. 3 Mike

    Isn’t there a way to just install postfix (for example) and have the wiki use that instead?
    I don’t understand what I am supposed to do with these script(s) …

  4. 4 Jeff

    Well, Mike, i assume, the most users of dekiwiki have allready postfix or same other mail transfer agent installed. Deki would neet this for example to send you a forgotten password. But this is not the point.
    The point is, that deki does not *yet* support sending you mails, if something has changed on your wiki. (The only thing that is supported, is RSS-Notifications, but this is very unflexible, and does not meet the point) Why would you want getting an email every time a wiki page has changed? Here is some basic example of how i’m using this.
    I have two part time emploees. Because we all work over the internet, we do not have fixed times for working. So the emploees can just decide on their own, when they want to work. But, because i pay them for this, of course i want to know, when they did worked and how long. So every emploee has a personal wiki page, where he makes notes about worked time in form of a list. I have this wiki page on my watchlist, so with the script that i have written above, i get an email with the woked time of the emploee. Deki is also very smart here, so it shows me really the changes made on this page, so get only the new entry of the list!

Leave a Reply