SRL Archive - SRL Member Goodies

Markov chain AI responder plugin


Yakman

2010-05-03 21:38:12

This plugin has great potential for use in runescape autoing. It could be
taught by coding a script to make a bot stand around a crowded area for many
hours until the contexts / word ratio becomes high. Also a bot could learn
just by going about its business mining / woodcutting and listening to people talk.
You might have to let it learn for quite a long time before its responses seem okay.

If any of you keep chatlogs (wt-f :p) you will find them very valuable. If you
code a script to simply parse your chatlogs and pass them to MarkovRespondLearn()
you will very quickly teach it.

As the responces depend entirely on what input it receives, i suspect the responder
will end up sounding a lot like the average runescape player.

I feel the best way to go about putting this into SRL is to have a certain probability
that the bot will respond. Coding a procedure to crudely count the number of other
players could lead to a nice way of deciding this probability. I recommend an
exponential function, along the lines of 'p = exp(-r * c)' where p is the probability
of talking, c is the player count and r is a small number to be determined. Make
sure there is a different higher probability for talking when the player's nick
is detected. Also it may be a good idea to detect questions like 'mining level'
and handle them seperatly.

Make sure you call MarkovRespondSaveWords() periodically, perhaps in NextPlayer()
as it can take a long time for large word lists. Also make sure you print out
the statistics like context / word ratio in progress reports.

Word lists could be shared with confidence as there is an element of randomness,
it will be unlikely that two bots meeting each other with the same responder
will become suspicious to anybody. Also as bots go around the world they will
learn slightly different things.



program new;
{.loaddll markovrespond}

{
Plugin documentation.

procedure MarkovRespondSetup(seed: Integer);
- Sets up responder. Seed is used to start a pseudorandom number gen.

procedure MarkovRespondLearn(line: string);
- Learns from the given line.

procedure MarkovRespondGenerateReply(line: string);
- Generates a responce using markov chains of the given line.
- Use MarkovRespondGetReply() to get the actual string.

function MarkovRespondGetReplyLength(): Integer;
- Returns the length of the string in characters of the responce. This must
- be used to call SetLength() on a string variable before it is passed
- to MarkovRespondGetReply()

procedure MarkovRespondGetReply(reply: string);
- Writes the reply string to the given parameter which must be prepared
- with SetLength()

function MarkovRespondSaveWords(filename: string): boolean;
- Saves the words learned to a file, can be loaded again
- with MarkovRespondLoadWords(), returns false if failed.

function MarkovRespondLoadWords(filename: string): boolean;
- Loads words from file that came from MarkovRespondSaveWords(), returns
- false if failed.

procedure MarkovRespondGetStats(var wordcount, contextcount, linecount: Integer);
- Gets the statistics of the internal state.
}

var
request, reply: string;

procedure MarkovPrintStats;
var
wordcount, contextcount, linecount: Integer;
begin
MarkovRespondGetStats(wordcount, contextcount, linecount);
if(wordcount = 0)then
wordcount:= 1;
writeln('I know ' + IntToStr(wordcount) + ' words (' +
IntToStr(contextcount) + ' contexts, ' +
FloatToStr(contextcount * 1.0 / wordcount) + ' per word), ' +
IntToStr(linecount) + ' lines.');
end;

begin
MarkovRespondSetup(Random($7fffffff));
MarkovRespondLearn('hello world');
MarkovRespondLearn('world hello');
{
if(not MarkovRespondLoadWords('lines.txt'))then
begin
writeln('failed to open file');
TerminateScript;
end;
}
MarkovPrintStats;

request:= 'hello'; //Readln('talk to the bot');
MarkovRespondGenerateReply(request);
SetLength(reply, MarkovRespondGetReplyLength);
MarkovRespondGetReply(reply);
writeln('reply:''' + reply + '''');

MarkovRespondSaveWords('lines.txt');
end.


Source and dll attached.

WT-Fakawi

2010-05-04 01:44:28

Very curious to see what will happen with this. So I can add this to any script and let Markov collect data?

nielsie95

2010-05-04 04:10:57

Am I doing it wrong or will this always reply a similar sentence to the request? I don't think that's a good thing to do for things like "what's up" and such.

Yakman

2010-05-04 07:26:58

nielsie95 I don't think it will do that when it learns a lot of words, but we'll have to try it and see. Get it up to ~5 contexts should be good enough to check.

wt-fakawi yes

edit: I've tried some of those question on the bot trained from the #srl irc channel. Bare in mind the chat style, spelling, grammar and level of intelligence of irc is very different to runescape.

> what's up
<Seeborg> what's your mom
> what's up
<Seeborg> frement : what's with all my dorm in ottawa during a tortoisegit
> what's up
<Seeborg> what's the inside and outside
> what's up
<Seeborg> so what's the difference between wizzup where could i install simba on a plug, i could do printf(\n) or puts() to print a bitch
> what's u
<Seeborg> what's the combo
> what's up
<Seeborg> what's making a markov chain?
> how's it going
<Seeborg> how's your dream, and npc's
> how's it going
<Seeborg> how's your tempur-pedic?
> how's it going
<Seeborg> how's your tempur-pedic?
> hows it going
<Seeborg> hows the seedbox
> hows it going
<Seeborg> jason2gs, so hows it going to have to my feet have gotten 200+posts since march 2009 the geographically seperated
>

mastaraymond

2010-05-04 21:03:00

Awesomeness! Will take a look at how to implement this in SRL(?) when I've got some time. Just wondering what will happen it it learns dutch/german/english at the same time :p.

Town

2010-05-05 16:55:03

Having a Dutch-speaking bot that recognises English sounds more effective than an English-speaking one. That sure would shorten the conversations…

Wizzup?

2010-05-08 18:00:10

Looks good. :)

Smartzkid

2010-05-14 00:00:45

/me wants a blog-reader->trainer

Yakman

2010-07-18 15:26:04

Moved to SRL members since the other place was filled with people who sadly have no time.

MylesMadness

2010-07-18 19:01:06

I can't seem to get this to work. It looks like simba never loads the plugin

Yakman

2010-07-18 19:37:20

put the plugin in the plugin folder?
restart simba?

what is the error message?

MylesMadness

2010-07-18 19:54:33

Its in the plugin folder, simba restarted, computer rebooted. Tried in wine and in windows 7. I just get [Error] (46:1): Unknown identifier 'MarkovRespondGetStats' at line 45

Cigue

2010-07-20 02:23:26

I like this :) could someone share wordlists or a learned markov list?

RAM

2010-07-23 14:45:03

I'm going to check this out more when I have time…..

Rick

2010-07-23 15:19:19

From: MylesMadness
Its in the plugin folder, simba restarted, computer rebooted. Tried in wine and in windows 7. I just get [Error] (46:1): Unknown identifier 'MarkovRespondGetStats' at line 45


I get the same error. The plugin hasn't loaded properly ?

Edit: Works in Scar .. not Simba though

Edit2: Loaded a large word list into it (Not the biggest I have). Should it be replying with a response very similar to the request ? I'm assuming a list of actual phrases works better.

[PHP]
Successfully compiled (39 ms)
Took 38906ms to load words
I know 3678016 words (3991059 contexts, 1.08511191903461 per word), 3808664 lines.
Request'yo'
reply:'yo'
Request'whatsup'
reply:'whatsup'
Request'what are you doing'
reply:'up and doing'
Request'mining'
reply:'mining'
[Runtime Error] : Exception: Access violation at address 76338E0F in module 'msvcrt.dll'. Write of address 00000000 in line 129 in script
[/PHP]

Ran into the error when responding to Runescape lol

Smartzkid

2010-08-07 04:04:44

This doesn't work properly in Simba, but for anyone who doesn't mind firing up SCAR, you can get some interesting results with the code below. And yes, I realize this does not produce practical results. It needs to be searched for regexes so any html formatting can be removed. But that's for another day.

The twitter feeds give, by far, the best results. I just picked a few random feeds that popped up and looked popular. Please, feel free to add your own and share them.

program new;
var
request, reply, page, temp: string;
urlList: array of string;
i, j, client: integer;

procedure MarkovPrintStats;
var
wordcount, contextcount, linecount: Integer;
begin
MarkovRespondGetStats(wordcount, contextcount, linecount);
if(wordcount = 0)then
wordcount:= 1;
writeln('I know ' + IntToStr(wordcount) + ' words (' +
IntToStr(contextcount) + ' contexts, ' +
FloatToStr(contextcount * 1.0 / wordcount) + ' per word), ' +
IntToStr(linecount) + ' lines.');
end;

begin
MarkovRespondSetup(Random($7fffffff));
SetLength(urlList, 6);
urlList[0] := 'http://twitter.com/favorites/113425681.rss';
urlList[1] := 'http://twitter.com/statuses/user_timeline/16303106.rss';
urlList[2] := 'http://twitter.com/statuses/user_timeline/15568127.rss';
urlList[3] := 'http://twitter.com/statuses/user_timeline/22461427.rss';
urlList[4] := 'http://twitter.com/statuses/user_timeline/138301078.rss';
urlList[5] := 'http://twitter.com/statuses/user_timeline/155478997.rss';


{
urlList[6] := 'http://rss.slashdot.org/Slashdot/slashdot';
urlList[7] := 'http://slashdot.org/firehose_popular.rss';
urlList[8] := 'http://www.instructables.com/tag/type-id/category-living/rss.xml';
urlList[9] := 'http://www.instructables.com/tag/type-id/category-outside/rss.xml';
urlList[10] := 'http://www.instructables.com/tag/type-id/category-play/rss.xml';
urlList[11] := 'http://www.instructables.com/tag/type-id/category-technology/rss.xml';
urlList[12] := 'http://www.instructables.com/tag/type-id/category-workshop/rss.xml';
urlList[13] := 'http://feeds.feedburner.com/Inhabitat';
urlList[14] := 'http://cgi.askmen.com/RSS/askmenRSS_main.php';
urlList[15] := 'http://cgi.askmen.com/RSS/askmenRSS_channel.php?ID=dating';
urlList[16] := 'http://cgi.askmen.com/RSS/askmenRSS_channel.php?ID=top_10';
urlList[17] := 'http://cgi.askmen.com/RSS/askmenRSS_channel.php?ID=toys';
urlList[18] := 'http://cgi.askmen.com/RSS/askmenRSS_channel.php?ID=fashion';
urlList[19] := 'http://cgi.askmen.com/RSS/askmenRSS_channel.php?ID=sports';
urlList[20] := 'http://cgi.askmen.com/RSS/askmenRSS_channel.php?ID=money';
urlList[21] := 'http://cgi.askmen.com/RSS/askmenRSS_channel.php?ID=cars';
urlList[22] := 'http://cgi.askmen.com/RSS/askmenRSS_channel.php?ID=fine_living';
urlList[23] := 'http://cgi.askmen.com/RSS/askmenRSS_channel.php?ID=blogs';
urlList[24] := 'http://cgi.askmen.com/RSS/askmenRSS_channel.php?ID=gossip';
urlList[25] := 'http://cgi.askmen.com/RSS/askmenRSS_channel.php?ID=cool_site&ID2=daily';
urlList[26] := 'http://cgi.askmen.com/RSS/askmenRSS_channel.php?ID=jokes&ID2=daily';
}

//urlList[] := '

MarkovRespondLoadWords('twitter.txt');

client := InitializeHTTPClient(false, false);
for i := 0 to High(urlList) do
begin
page := GetHTTPPage(client, urlList[i]);
while(Pos('<description>', page) > 0) do
begin
temp := Between('<description>', '</description>', page);
temp := Replace(temp, '&lt;', '<');
temp := Replace(temp, '&gt;', '>');
while((pos('<', temp) > 0) and (pos('>', temp) > 0)) do
temp := Replace(temp, '<' + Between('<', '>', temp) + '>', '');
while((pos('&', temp) > 0) and (pos(';', temp) > 0)) do
temp := Replace(temp, '&' + Between('&', ';', temp) + ';', '');
while(pos(' ', temp) > 0) do
temp := Replace(temp, ' ', ' ');
temp := Replace(temp, '*r-t-w-e-e-t*', '');
temp := Replace(temp, '''', '');
temp := Replace(temp, ':)', '');
temp := Replace(temp, ':(', '');
if(Pos(':', temp) > 0) then
begin
j := LastPos(':', temp);
temp := Copy(temp, j + 1, Length(temp) - j + 2);
end;
temp := Trim(temp);
MarkovRespondLearn(temp);
j := Pos('</description>', page) + 14;
page := Copy(page, j, Length(page) - j);
end;
ClearDebug;
MarkovPrintStats;
end;

request:= 'lady gaga'; //Readln('talk to the bot');
repeat
ClearDebug;
writeln('Request: ' + request);
for i := 1 to 5 do
begin
MarkovRespondGenerateReply(request);
if(MarkovRespondGetReplyLength > 0) then
begin
SetLength(reply, MarkovRespondGetReplyLength);
MarkovRespondGetReply(reply);
reply := Trim(reply);
writeln('Reply ' + IntToStr(i) + ':''' + reply + '''');
end;
end;
request:= Readln('talk to the bot');
until(request = '')

MarkovRespondSaveWords('twitter.txt');
end.

From: Request: obamas
reply 1:'austin, texas is celebrating obamas birthday with a luau'
reply 2:'austin, texas is celebrating obamas birthday with a rock star!'
reply 3:'austin, texas is celebrating obamas birthday with a daily basis'
reply 4:'the fucken anti-christ is celebrating obamas birthday with a hoagie in a horns'
reply 5:'the world is celebrating obamas birthday with a rodent orifice.'


EDIT: Here is an on-demand twitter-searcher-learner (yes, horribly ugly code):
program new;
var
request, reply, page, line: string;
urlList: array of string;
i, j, client: integer;

procedure MarkovPrintStats;
var
wordcount, contextcount, linecount: Integer;
begin
MarkovRespondGetStats(wordcount, contextcount, linecount);
if(wordcount = 0)then
wordcount:= 1;
writeln('I know ' + IntToStr(wordcount) + ' words (' +
IntToStr(contextcount) + ' contexts, ' +
FloatToStr(contextcount * 1.0 / wordcount) + ' per word), ' +
IntToStr(linecount) + ' lines.');
end;

procedure ProcessText;
begin
while(Pos('<description>', page) > 0) do
begin
line := Between('<description>', '</description>', page);
line := Replace(line, '&lt;', '<');
line := Replace(line, '&gt;', '>');
while((pos('<', line) > 0) and (pos('>', line) > 0)) do
line := Replace(line, '<' + Between('<', '>', line) + '>', '');
while((pos('&', line) > 0) and (pos(';', line) > 0)) do
line := Replace(line, '&' + Between('&', ';', line) + ';', '');
while(pos(' ', line) > 0) do
line := Replace(line, ' ', ' ');
line := Replace(line, '*r-t-w-e-e-t*', '');
line := Replace(line, '''', '');
line := Replace(line, ':)', '');
line := Replace(line, ':(', '');
if(Pos(':', line) > 0) then
begin
j := LastPos(':', line);
line := Copy(line, j + 1, Length(line) - j + 2);
end;
line := Trim(line);
MarkovRespondLearn(line);
j := Pos('</description>', page) + 14;
page := Copy(page, j, Length(page) - j);
end;
end;

begin
MarkovRespondSetup(Random($7fffffff));
MarkovRespondLoadWords('twitter.txt');

client := InitializeHTTPClient(false, false);
request:= 'lady gaga'; //Readln('talk to the bot');
repeat
ClearDebug;
writeln('Request: ' + request);

page := GetHTTPPage(client, 'http://search.twitter.com/search.rss?q=' + request);
ProcessText;

for i := 1 to 5 do
begin
MarkovRespondGenerateReply(request);
if(MarkovRespondGetReplyLength > 0) then
begin
SetLength(reply, MarkovRespondGetReplyLength);
MarkovRespondGetReply(reply);
reply := Trim(reply);
writeln('Reply ' + IntToStr(i) + ':''' + reply + '''');
end;
end;
MarkovPrintStats;
request:= Readln('talk to the bot');
until(request = '')

MarkovRespondSaveWords('twitter.txt');
FreeHTTPClient(client);
end.

Heavenguard

2010-08-07 22:34:44

Wow this looks intricate, thanks so much Yakman, and thanks for the great feedback Smartzkid.

Wizzup?

2010-08-09 07:59:25

Looks pretty good Smartz. Any idea why it doesn't work in Simba?

Rick

2010-08-09 12:17:10


{.loaddll markovrespond}

should be


{$Loadlib markovrespond}

then it works in Simba.

Yakman

2010-08-11 10:34:49

Simba must've updated since then. I developed this entirely on Simba.

I'm pleased about this, especially the twitter thing.

Smartzkid

2010-08-12 01:28:29

Does anyone know of a way to get a list of public twitter feeds?

I'll be away until tuesday, but I hope to be surprised when I come back ;)

nielsie95

2010-08-12 06:18:23

You could use the top tweets feed to get some accounts?

mastaraymond

2010-08-12 12:58:06

From: Smartzkid
Does anyone know of a way to get a list of public twitter feeds?

I'll be away until tuesday, but I hope to be surprised when I come back ;)

Search on popular keywords, you get a list of accounts connected to those..

Smartzkid

2010-08-17 22:37:46

TwitterUsers.simba

program new;

function GetTrendingTopics(): Array of String;
var
mainPage: String;
high, temp: integer;
begin
mainPage := GetPage('http://twitter.com/');
high := -1;
SetLength(result, 10);

while(Pos('search_link', mainPage) > 0) do
begin
high := high + 1;
if(high >= Length(result)) then
SetLength(result, high + 10);
result[high] := Between('<a href="http://twitter.com/search?q=', '" class="search_link"', mainPage);
temp := Pos('" class="search_link"', mainPage) + 22;
mainPage := Copy(mainPage, temp, Length(mainPage) - temp);
end;

SetLength(result, high + 1);
end;

function GetUsersFromSearch(searchString: String): Array of String;
var
page: String;
{high,} temp, i: integer;
begin
page := GetPage('http://mobile.twitter.com/searches?q=' + searchString);
temp := Pos('<div id=''results''></div>', page);
page := Copy(page, temp, Length(page) - temp);
//high := -1;
SetLength(result, 20);

for i := 0 to 19 do
begin
//high := high + 1;
//if(high >= Length(result)) then
// SetLength(result, high + 10);
result[i {high}] := Between('">', '</a>', Between('<strong>', '</strong>', page));
temp := Pos('</strong>', page) + 9;
page := Copy(page, temp, Length(page) - temp);
end;

//SetLength(result, high + 1);
end;

function GetTrendingUsers(): Array of String;
var
trends: Array of String;
users, temp: Array of String;
i, j, offset: integer;
begin
trends := GetTrendingTopics();
for i := 0 to high(trends) do
begin
temp := GetUsersFromSearch(trends[i]);
offset := Length(users);
SetLength(users, Length(users) + Length(temp));
for j := 0 to high(temp) do
users[j + offset] := temp[j];
end;

result := users;
end;

function RemoveDuplicates(a: Array of String): Array of String;
var
i, j, n: integer;
begin
SetLength(result, Length(a));
for i := 0 to high(a) do
begin
for j := i + 1 to high(a) do
if(a[i] = a[j]) then
break;
if(j = high(a) + 1) then
begin
result[n] := a[i];
n := n + 1;
end;
end;
SetLength(result, n);
end;


var
t: Array of String;
i, ms: integer;

begin
ClearDebug;
ms := GetSystemTime;
t := RemoveDuplicates(GetTrendingUsers());
ms := GetSystemTime - ms;
writeln('Got ' + IntToStr(Length(t)) + ' users in ' + IntToStr(ms) + 'ms.');

for i := 0 to high(t) do
writeln(t[i]);
end.

It is worth noting that each of these users also has a quote that can be learned, plus a collection of 20 quotes per page on their tweets page.

Goobs

2010-09-15 19:35:07

I had a look at this, and this is what I've come up with. Feel free to chop it and change it as necessary. It uses a random word generator I pulled from the depths of the internets with a previously posted script. I only take credit for the changes I made.

{$Loadlib markovrespond}

program new;
var
request, reply, page, line: string;
urlList: array of string;
i, j, client, II: integer;
nones : TReplaceFlags;

procedure MarkovPrintStats;
var
wordcount, contextcount, linecount: Integer;
begin
MarkovRespondGetStats(wordcount, contextcount, linecount);
if(wordcount = 0)then
wordcount:= 1;
writeln('I know ' + IntToStr(wordcount) + ' words (' +
IntToStr(contextcount) + ' contexts, ' +
FloatToStr(contextcount * 1.0 / wordcount) + ' per word), ' +
IntToStr(linecount) + ' lines.');
end;

function rword: String;
var
tt: Integer;
pp: String;
begin
tt := InitializeHTTPClient(False);
pp := GetPage('http://www.ypass.net/misc/dictionary/random-word.php.json');
pp := Between('{"word":"', '","part"', pp);
If pp <> '' then result := pp else Result := '';
FreeHTTPClient(tt);
end;

procedure ProcessText;
begin
while(Pos('<description>', page) > 0) do
begin
line := Between('<description>', '</description>', page);
line := Replace(line, '&lt;', '<', nones);
line := Replace(line, '&gt;', '>', nones);
while((pos('<', line) > 0) and (pos('>', line) > 0)) do
line := Replace(line, '<' + Between('<', '>', line) + '>', '', nones);
while((pos('&', line) > 0) and (pos(';', line) > 0)) do
line := Replace(line, '&' + Between('&', ';', line) + ';', '', nones);
while(pos(' ', line) > 0) do
line := Replace(line, ' ', ' ', nones);
line := Replace(line, '*r-t-w-e-e-t*', '', nones);
line := Replace(line, '''', '', nones);
line := Replace(line, ':)', '', nones);
line := Replace(line, ':(', '', nones);
if(Pos(':', line) > 0) then
begin
j := Pos(':', line);
line := Copy(line, j + 1, Length(line) - j + 2);
end;
line := Trim(line);
MarkovRespondLearn(line);
j := Pos('</description>', page) + 14;
page := Copy(page, j, Length(page) - j);
end;
end;

begin
Simba;
WriteLN('FOR USE WITH SIMBA ONLY');
Wait(3000);
ClearDebug;
MarkovRespondSetup(Random($7fffffff));
MarkovRespondLoadWords('twitter.txt');

client := InitializeHTTPClient(false);
request:= rword;
repeat
repeat
ClearDebug;
writeln('Request: ' + request);

page := GetHTTPPage(client, 'http://search.twitter.com/search.rss?q=' + request);
ProcessText;

for i := 1 to 5 do
begin
MarkovRespondGenerateReply(request);
if(MarkovRespondGetReplyLength > 0) then
begin
SetLength(reply, MarkovRespondGetReplyLength);
MarkovRespondGetReply(reply);
reply := Trim(reply);
writeln('Reply ' + IntToStr(i) + ':''' + reply + '''');
end;
end;
MarkovPrintStats;
request:= rword;
Inc(II);
until(II >= 20)
MarkovRespondSaveWords('twitter.txt');
Wait(50);
MarkovRespondLoadWords('twitter.txt');
II := 0;
until(false);
FreeHTTPClient(client);
end.

Smartzkid

2010-09-16 20:54:23

I had some trouble with getting it to run correctly, but I love the idea!

Goobs

2010-09-17 06:31:51

My main issue was getting the phrase grabbing to work better. I wanted it to get 10 results per page instead of 5, doubling the chances of getting a good context or phrase. I didn't know how to do this as the source for those twitter rss feeds is a little confusing. The random word / phrase generator that I implemented works out fine for me, though.

Gu3st...

2010-09-17 13:31:52

Hum hum, this is extremely interesting!
I'll take a closer look at this later :)

Goobs

2010-09-17 13:52:46

what I like about the site I used to grab the random phrases is that it also includes phrases, so that helps a bit with the context. The text grabbing from Twitter could use improvement, though that's a bit beyond me. If that would work well we could have a HUGE database compiled in next to no time if it were run on 4-5 computers.

Gu3st...

2010-09-17 18:32:43

We could also try running this on Omegle.com and such sites and let it learn from that.
Also AI .php bots like smarterchild is good for learning this responder :)

Goobs

2010-09-18 09:47:00

you know, I didn't think about that! If we did that, we'd actually get good context instead of twitter spam.

Camaro'

2010-09-18 13:40:38

From: Zyt3x
We could also try running this on Omegle.com and such sites and let it learn from that.
Also AI .php bots like smarterchild is good for learning this responder :)


I'm going to write an Omegle conversation grabber and post it later for anyone who wants it.

Goobs

2010-09-18 18:22:14

I want it - could compile a huge database that way

Camaro'

2010-09-18 21:36:46

From: ProphesyOfWolf
I want it - could compile a huge database that way


Can you add my MSN - [email]air_mc_roberts@hotmail.com[/email]

Gu3st...

2010-09-19 03:37:21

markovrespond.dll is crashing all the time… I get to run this for about 10 minutes and then simba crashes

Goobs

2010-09-19 22:02:35

Gmail'd

moparisthebest

2010-09-22 12:23:06

I don't suppose anyone knows if this works on 64-bit windows do they? (I also noticed it won't work under Linux without some modifications)

Gu3st...

2010-10-02 14:40:24

I have no idea why this keeps crashing for me……. Any suggestions?