29. Oktober 2009

How to Reactivate Weblin with 2 XML Files

In the forum of the Open Virtual World project someone posted a way to reactivate Weblin. Based on this information and after my own tests I can confirm, that it works.

It is even simpler and works without programming. All you need is a Web space to put some files.

You create a Jabber account on a public Jabber server, (e.g. http://register.jabber.org). Then put some files on your Web space and configure Weblin to use them. You need 2 XML files and an image as avatar. If you want an animated avatar then you need an additional XML and some animated GIFs.

Quick Try:

Enter in the Windows registry ...

Restart Weblin .. and you are Planta. But, we do not want to be Planta, therefore to create your own...


The 2 most important files are login.xml and identity.xml. I have put both as examples on my web server:
In login.xml is the URL of identity.xml and you have to enter the Jabber account: Jabber passwort ("secret"), Jabber user name ("planta") und Jabber server ("jabber.org").

In identity.xml is a link to the avatar image:
Now you tell Weblin to use login.xml at startup. You set the registry key like this:
Of course, that's the URL of your web space.

Where do you get the software from? Just copy the folder "C:\Program Files\weblin" from a friend. There is no installer required.

For Advanced Users:

Because the PlatformLoginURL is public, anyone can use this avatar (and the Jabber account). The Weblin login screen requests a password. It sends the password, but your web space probably returns the login.xml without checking the password. If you want to protect your login.xml, then you must create a login.php instead of the login.xml. Your login.php can check the HTTP Basic Authentication. You could also protect the login.xml with .htaccess. Attention: .htaccess protects the entire folder. Do not accidentally deny access to the other files. Anyone must be able to fetch your identity.xml and avatar image. You can put login.xml into a separate folder and set PlatformLoginURL accordingly.

For Advanced Users:

identity2.xml has an additional link to an animation file:
which links to animated GIFs. This provides your weblin with actions and animations.

<config xmlns="http://schema.bluehands.de/character-config" version="1.0">
  <sequence group="idle" name="idle1" probability="1000" type="status" in="standard" out="standard">
    <animation src="still.gif" />
  <sequence group="moveleft" name="moveleft1" probability="1000" type="basic" in="moveleft" out="moveleft">
    <animation dx="-100" src="left.gif" />
  <sequence group="moveright" name="moveright1" probability="1000" type="basic" in="moveright" out="moveright">
    <animation dx="100" src="right.gif" />
For the start copy this file and use your own GIFs. The XML and the GIFs are cached by the client. If you change something, you have to delete the client cache (C:\Documents and Settings\%USERNAME%\Application Data\zweitgeist\cache).

avatar.xml format in short:
  • <config> has multiple <sequence>
  • <sequence> has one <animation>
  • <sequence> has an attribut "group". This is the action, walk, stand, sleep, wave. If there are multiple actions in the same "group", then Weblin searches a <sequence> randomly controlled by the relative "probability".
  • <sequence> has an attribut "name". Must be unique, but can be anything.
  • <sequence> has an attribut "probability": relative frequency of the <sequence> in a "group"
  • <sequence> has an attribut "type" with values "status", "basic", "emote". "emote" appear as actions in the avatar-menu.
  • <sequence> attributes "in" and "out" can be ignored. They are for advanced uses with automatic selection of transitions between actions.
  • <animation> has only the attribut "src". An animated GIF URL absolute or relative the XML file


23. Oktober 2009

Database as a Backend Web Service

The database is always the bottleneck. This is what all the admins of massive services tell in talks about their scaling efforts.

In short:

  • Database used to mean SQL
  • It is difficult to scale SQL CPU
  • It is simple to scale Web-Frontend CPU
  • The SQL philosophy puts the burden on Read by enabling very complex SELECTs and JOINs while Write is usually simple with short INSERT. Just the wrong concept in a massive world. We need quick and simple read operations, not complex reporting features.
Therefore many people step back from SQL and use other databases. Read more about the NoSQL movement. You have the choice: CouchDB, MongoDB, Tokyo Tyrant, Voldemort, Cassandra, Ringo, Scalaris, Kai, Dynomite, MemcacheDB, ThruDB, Cassandra, HBase, Hypertable, AWS SimpleDB, or just use Amazon S3 as stupid document store. Also SQL can be 'misused' as quick document/key-value oriented storage. It still has some key benefits.

Basically all you need is a key-value collection store with some indexing, alias document store. Whatever you decide: you are bound to it and this sucks. So, why not decouple the application logic from the database? Decoupling can be done in different ways. Traditionally you had a thin database code layer that tried to abstract from different (SQL) databases. Now, I need more abstraction, because there might well be a non-SQL database in the mix.

I decided to put a web service style frontend-backend separation between application code and database. This makes the DB a web service. In other words: There is HTTP between application and DB which allows for massive scaling. Eventually, my DBs can be scaled using web based load balancing tools. This is great. I can also swap out the DB on a per table basis for another database technology. Also great, because I do not have to decide about the database technology now and this is what this article really is about, right?

So, now I design the DB web service interface. I know what I need from the database interface. This are the requirements:
  1. Database items (think: rows) are Key-Value collections
  2. Sparse population: not all possible keys (think: column names) exist for all items
  3. One quick primary key to access the collection or a subset of key-values per item
  4. Results are max one item per request. I will emulate complex searches and multi-item results in the application (disputed by Ingo, see Update 1)
  5. Required operations: SET, GET, DELETE on single items
  6. Support auto-generated primary keys
  7. Only data access operations, no DB management.

This is the interface as code:

  1. interface IStorageDriver
  2. {
  3. // Arguments:
  4. // sType: Item type (think: table).
  5. // properties: The data. Everything is a string.
  6. // names: Column names.
  7. // condition: A simple query based on property matching inside the table. No joins. Think: tags or WHERE a=b AND c=d

  8. // Add an item and return an auto created ID
  9. string Add(string sType, Dictionary<string, string> properties);
  10. // returns Created ID

  11. // Set item properties, may create an item with a specified ID
  12. void Set(string sType, string sId, Dictionary<string, string> properties);

  13. // Fetch item properties by ID or condition, may return only selected properties
  14. Dictionary<string, string> Get(string sType, string sId, List<string> names);
  15. List<Dictionary<string, string>> Get(string sType, Dictionary<string, string> condition, List<string> names);
  16. // returns The data. Everything is a string

  17. // Delete an item by ID
  18. bool Delete(string sType, string sId);
  19. // returns True = I did it or False = I did not do it, because not exist, result is the same
  20. }

I added the "Add" method to support auto-generated primary keys. Basically, "Set" would be enough, but there are databases or DB schemes which generate IDs on insert, remember?

All this wrapped up into a SRPC interface. Could be SOAP, but I do not want the XML parsing hassle (not so much the overhead). WSDLs suck. Strong typing of web services is good, but can be replaced by integration tests under adult supervision.

On the network this looks like:

  1. POST /srpc HTTP/1.1
  2. Content-length: 106

  3. Method=Data.Add
  4. _Type=TestTable
  5. User=Planta
  6. Age=3
  7. Identity=http://ydentiti.org/test/Planta/identity.xml

  1. HTTP/1.1 200 OK
  2. Content-length: 19

  3. Status=1
  4. _Id=57646

Everything is a string. This is the dark side for SQL people. The application knows each type and asserts type safety with integration tests. On the network all bytes are created equal. They are strings anyway. The real storage drivers on the data web service side will convert to the database types. The application builds cached objects from data sets and maps data to internal types. There are no database types as data model in the application. Business objects are aggregates, not table mappings (LINQ is incredibly great, but not for data on a massive scale).

BUT: I could easily (and backward compatible) add type safety by adding type codes to the protocol, e.g. a subset of XQuery types or like here:

  1. User=Planta
  2. User/Type=text
  3. Age=3
  4. Age/Type=int32
  5. Identity=http://ydentiti.org/test/Planta/identity.xml
  6. Identity/Type=url

The additional HTTP is overhead. But SQL connection setup is bigger and the application is INSERT/UPDATE bound anyway, because memcache will be used massively. Remember the coding rule: the database never notices a browser reload.

Now, I can even use AWS S3, which is the easiest massively scalable stupid database, or Simple DB with my data web service on multiple load balanced EC2 instances. I don't have to change anything in the application. I just implement a simple 4-method storage driver in a single page. For the application it is only 1 line configuration to swap the DB technology.

I can proxy the request easily and do interesting stuff:
  • Partitioning. User IDs up to 1.000.000 go to http://zero.domain.tld. The next million goes to go to http://one.domain.tld.
  • Replication: All the data may be stored twice for long distance speed reasons. The US-cluster may resolve the web service host name differently than the EU cluster. Data is always fetched from the local data service. But changes are replicated to the other continent using the same protocol. No binary logs across continents.
  • Backup: I can duplicate changes as backup into another DB, even into another DB technology. I don't know yet how to backup SimpleDB. But if I need indexing and want to use SimpleDB, then I can put the same data into S3 for backup.
  • Eventual persistence:The data service can collect changes in memory and batch-insert them into the real database.
All done with Web technologies and one-pagers of code and the app won't notice.

Update 1:

Supporting result sets (multi-item) as 'Get' response might be worth the effort. I propose to have 2 different 'Get' operations. The first with the primary key and no condition. This will always return at most 1 item. A second 'Get' without pimary key but with condition might return multiple items. (Having both, a primary key and a condition in the 'Get' makes no sense anyway). The multi-item response will use the SRPC Array Response.

On the network:

  1. POST /srpc HTTP/1.1
  2. Content-length: ...

  3. Method=Data.Get
  4. _Type=TestTable
  5. _Condition=Age=3\nGender=male
  6. _Names=Nickname Identity

Comment: _Condition is a key-value list. This is encoded like an 'embedded' SRPC. A key=value\n format with \n escaping to get it on a single line. _Names is a value list. Tokens of a value lists are separated by a blank (0x20) and blanks inside tokens are escaped by a '\ '. Sounds complicated, but easy to parse and read.

  1. HTTP/1.1 200 OK
  2. Content-length: ...

  3. Status=1
  4. 0:Planta
  5. 0:Identity=http://ydentiti.org/test/Planta/identity.xml
  6. 1:Wolfspelz
  7. 1:Identity=http://wolfspelz.de/identity.xml

I am not yet decided about queries with multiple primary keys. They could be implemented as
  1. SRPC Batch with multiple queries in a single HTTP request, or
  2. with a specific multi-primary-key syntax, similar to SQL: "WHERE id IN (1,2,3)".
The response would be almost identical, because a SRPC Batch response is very much like SRPC Array Response. Solution 2 adds a bit of complexity to the interface with a new multi-key request field. Solution 1 does not need an interface extension, but puts the burden on the data webservice, which must re-create multi-key semantics from a batch of single-key queries for optimal database access.

Update 2:

I agree with Ingo, that solution 1 (SRPC Batch) makes all operations batchable and has a simple interface at the same time. The trade off, that the webservice must detect multi-key semantics from a batch is probably not too severe. Clients will usually batch ony similar requests together. For the beginning the webservice can just execute multiple database transactions. Later the webservice can improve performance with a bit of code that aggregates the batch into a single multi-key database request.

Update 3:

In order to allow for later addition of type safety and other yet unknown features, I define here, now and forever, that SRPC keys with "/" (forward slash) be treated as meta-data for the corresponding keys without "/". Specifically, that they should not be treated as database (column) names. That's no surprise from the SRPC point of view, but I just wanted to make that clear. I have no idea why someone would use "/" in key names anyway. I find even "_" and "-" disturbing. By the way: ":" (colon) is also forbidden in keys for the benefit of SRPC Batch. In other words: please use letters and numbers, the heck.

Update 4:

I removed the "Database". "Type" is enough for the data service to figure out where to look for the data. "Type" is a string. It can contain "Database/Table".


Google Toolbar Search History Menu is a Browser

I recently installed a Google toolbar to test Sidewiki. The toolbar replaces the search box by Google's own even if the toolbar is hidden.

Of course, Google has a better search box. It's popup menu has styled text and links. Probably cound even show videos. Guess what, the popup menu is an embedded browser.

The funny thing is, that weblin regards the popup menu as the frontmost browser window and the avatar jumps to the base of the window. As soon as the popup opens while typing, the avatars jump there.

It actually IS the front most browser window and weblin is right. Weblin also correctly calculates the base offset and moves the entire scene to the popup. Great stuff for insiders. Well done by those who worked on the brower positioning code over time.


15. Oktober 2009

Aktuelle Metaprognosen zur Wirtschaftsentwicklung

Die Grafiken zeigen ab jetzt immer den aktuellen Stand:

Die Prognosen haben sich deutlich erholt. Das ging so schnell, dass sich die Diskussion um Krümmungsradien der ersten Artikel erübrigt hat. Es ist erstaunlich, wie sich die Prognosen an die Stimmung in der Wirtschaft anlehen. Eigentlich sollten sich die Prognosen aus Modellen ableiten und nicht nur aus Umfragen. Klar, Wirtschaft ist viel Psychologie, aber die Börse scheint in den letzten 18 Monaten besser die Zukunft zu prognostizieren, als die Wissenschaftler.

Während der Krise gingen die Prognosen leicht ins Minus, haben sich aber wieder erholt. Allerdings darf man diese Erholung nicht überbewerten, denn je tiefer der Fall in 2009, desto größer das Korrekturpotential in den Folgejahren. Ein 2 prozentiges Wachstum nach einem -5 % Fall hört sich nach solidem wiedererstarktem Wachstum an. Tatsächlich liegt die Wirtschaftsleistung aber noch 3 % geringer als 2 Jahre zuvor, während man in guten Zeiten eher +5 % für 2 Jahre erwartet. Also trotz scheinbar erholter guter 2 % eine Diskrepanz von 8 % zu einem guten Verlauf.

Februar 2009
April 2009
Juni 2009
Oktober 2009


9. Oktober 2009

Prior Art for the Eolas AJAX Patent

Eolas claims, that the U.S. Patent No. 7,599,985 covers AJAX and other mechanisms, which execute in the client and communicate with the server to update parts of the page.

The patent was filed August 9, 2002. How can the patent cover AJAX ? AJAX was invented by Microsoft and published in Internet Explorer 5 in March 1999. The next version, IE 6 in August 27, 2001 had the updated (final) version of the XmlHttpRequest object. IE6 also had a fully developed DOM model. Microsoft used these features for interactive Web applications like Outlook Web Access. Actually, some say the XmlHttpRequest object and the "technology" has been specifically developed for Outlook Web Access (OWA). OWA was available in Exchange 2000.

So, there was at least a product with documentation available. Even if the product handbook does not disclose the "technology", there are for sure developer documents, which explain XmlHttpRequest.

I know there were, because we used XmlHttpRequest in a project in 2002 before the patent was filed, before XmlHttpRequest became known as AJAX and before AJAX lost it's X(ML) and became AJAJ (Asynchronous HTTP and JSON).

Screenshot of the project, where a bluehands project for a german energy company violated the Eolas patent before it was even filed.


7. Oktober 2009

6. Oktober 2009

Scripting Configuration for C#

Configuration is a scripting task.

Since I am fed up with the bracket mess of mono/.NET web apps, I was thinking about a more convenient configuration system. Most configuration systems lack flexibility, because they just evaluate a static file and set some values. Some allow for includes, but not much more. But advanced applications need more flexibility, in other words: code. If the config file is a script and has access to the application, then you have all the flexibility you need, and type safety.

And what is the most natural config script language for a C# project? C#, of course.

All you need is a script engine like CS-Script. Add the script engine, create a script host, load the config script file on application start and execute it. The best part is, that the configuration file is type safe and can be edited by the usual development system.

Here are my code bits:

Add the CSScriptLibrary.dll to the project References.

Create a class that implements the script file loader: ScriptedConfig.cs

using System;

namespace MyApp
public class ScriptedConfig : MarshalByRefObject
public string BasePath { get; set; }

public void Include(string sFile)
using (CSScriptLibrary.AsmHelper helper =
new CSScriptLibrary.AsmHelper(CSScriptLibrary.CSScript.Load(
BasePath + sFile, null, true)))
helper.Invoke("*.Configure", this);
A class that implements my specific configuration object derived from ScriptedConfig: MyConfig.cs:
using System;
using System.Collections.Generic; // just for the sample Dictionary

namespace MyApp
public class MyConfig : ScriptedConfig
// Just some sample values
public string sText1_ = "empty1";
public string sText2_ = "empty2";

// A sample dictionary to hold more values
public Dictionary<string, string> values_ = new Dictionary<string, string>();
public Dictionary<string, string> Values { get { return values_; } }

public string Text2 { get { return sText2_; } }
The application startup code:

MyConfig myconfig = new MyConfig ();
myconfig.BasePath = AppDomain.CurrentDomain.BaseDirectory; // For my web app
I have a global config file, that is the same for all installations and a local config file that overrides the global setting for the test syste, the stage system or the production system. This is the ConfigGlobal.cs:
using System;
using MyApp;

public class Script
public static void Configure(MyConfig c)
c.sText1_ = "Global Config 1";
c.sText2_ = "Global Config 2";

The global config file invokes the local config file, which configures the same object. ConfigLocal.cs:
using System;
using MyApp;

public class Script
public static void Configure(MyConfig c)
c.sText2_ = "Local Config 2";
c.values_["Text3"] = "Local Config 3";
You access the config values like:

string t1 = myconfig.sText1_;
string t2 = myconfig.Text2;
Actually, in my current project I separate my code from the implementation of MyConfig by a facade pattern using an interface (IConfig) and in addition access the config instance via StructureMap and a global static wrapper (Config).

It rather looks like:

string t1 = Config.Get("Text1", "Default1");
string t3 = Config.Instance().Values["Text3"];
But that's for later.


2. Oktober 2009

Copying Large Files on Windows

Copying large (>50 GB) files seems to be a problem on Windows (XP). Windows Explorer fails after some time with "Insufficient system resources exist to complete the requested service. (error 1450)". I use GoodSync as a folder syncing program. It has the same problem. I even have a commercial license, because I use it for software deployment on WebDAV and other remote file systems. Suprisingly this commercial file copy program also fails after about 50 GB.

GoodSync support told me "i am not sure why you expect that windows can handle 85 gb file". Good joke. This is the 21st century, guys. Why should it not? Apparently, GoodSync uses the Windows function CopyFileEx. Probably the same as Windows Explorer. I don't know what's wrong with CopyFileEx. There is an old MSDN knowledge base article (kb259837), which admits a problem and continues, that it has been fixed in Windows 2000 SP2. Since Win XP is just an updated Win 2k (XP major version 5.1 compared to 5.0, Vista is 6.0), I suppose the problem is also fixed in XP.

I wrote a simple copy program using the POSIX API (fread/fwrite) which uses Win32 ReadFile under the hood instead of CopyFileEx. Just works. It does as many MB/sec as physically possible, and uses only 1.6 MB memory. So, what's the problem?

Considered my half page of code, CopyFileEx really sucks. The funny part is, that while copying large files in Windows Explorer makes the PC unresponsive and is no fun, my small program copies peacefully in the background. I am so good. Or is CopyFileEx just so bad? Probably the latter. There is nothing special with my code. No fancy tricks, just the basic 1st year CS bachelor stuff.

Lets call it "scopy" for simple copy because all things I do are not stupid, but simple:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char* argv[])
if (argc != 3) { printf("Usage: %s src dst\n", argv[0]); return 1; }

char const *szSrc = argv[1];
char const *szDst = argv[2];

FILE* fSrc = fopen(szSrc, "rb");
if (fSrc == 0) { printf("Error opening %s\n", szSrc); return 1; }

FILE* fDst = fopen(szDst, "wb");
if (fDst == 0) { printf("Error opening %s\n", szDst); return 1; }

size_t nSize = 1024*1024;
unsigned char* pBuf = new unsigned char[nSize];
if (pBuf == 0) { printf("Error allocating %d bytes\n", nSize); return 1; }

int nCnt = 0;
__int64 nTotal = 0;

bool bDone = false;
while (!bDone) {
size_t nRead = fread(pBuf, 1, nSize, fSrc);
if (ferror(fSrc)) {
perror("Read error"); return 1;
} else {
size_t nWritten = fwrite(pBuf, 1, nRead, fDst);
if (ferror(fDst)) {
perror("Write error"); return 1;

nTotal += nWritten;

if (feof(fSrc)) {
bDone = true;
printf("\n%I64d\n", nTotal);
} else {
printf("%d ", nCnt);

delete[] pBuf;
pBuf = 0;


//char c = getchar();

return 0;


1. Oktober 2009

Weblin wiederbeleben mit 2 XML Dateien

Im Forum des Open Virtual World Projekts hat jemand einen Weg gepostet mit dem man Weblin wieder betreiben kann obwohl die Server abgeschaltet worden sind. Basierend auf diesen Informationen und nach meinen eigenen Tests kann ich bestätigen, dass es geht.

Es ist sogar noch einfacher und ohne Programmierkenntnisse.

Man muss nur auf einem öffentlichen Jabber Server einen Jabber Account anlegen (z.B. http://register.jabber.org). Dann auf einem Web-Space ein paar Dateien hinlegen und Weblin konfigurieren, dass es diese Dateien benutzt. Man braucht 2 XML Dateien. Dann natürlich ein Bild als Avatar und je nachdem, ob man ein animiertes Avatar will, noch ein XML mit GIF Animationen.

Schnell mal ausprobieren:

In Registry eintragen...

Weblin neu starten ... und du bist Planta. Aber wir wollen ja nicht jeder Planta sein, deshalb...

So geht es:

Die 2 wichtigsten Dateien sind login.xml und identity.xml. Als Beispiele habe ich diese 2 Dateien gemacht und auf meinen eigenen Webserver gelegt:
In login.xml steht der URL von identity.xml und man muss den JabberAccount, also Jabber Passwort ("secret"), Jabber Username ("planta") und Jabber Server ("jabber.org") eintragen.

In identity.xml ist ein Link auf das Avatarbild:
Jetzt muss man nur noch Weblin beibringen, dass es das login.xml benutzt beim Starten. Dazu einen Registry Key setzen. Beispiel:
Natürlich muss hier der richtige URL von deinem Web-Space stehen. Vielleicht programmiert Marmel ja ein Tool dafür :-)

Ach ja, wo bekommt man die Software her? Einfach von eine(r|m) Freund(in|) den Ordner C:\Programme\weblin kopieren. Kein Installer nötig.

Für Fortgeschrittene:

Da der PlatformLoginURL öffentlich ist, kann jeder dieses Avatar (und den Jabber Account) benutzen. Weblin fragt beim Start zwar ein Passwort. Das wird aber nicht verwendet, da bei PlatformLoginURL ein statischer URL eingetragen ist und nicht eine echte Web-Applikation, die das Passwort prüfen würde. Wenn man nicht will, dass andere das gleiche Avatar verwenden, muss man wie in dem Forum Post vorgehen und statt login.xml ein login.php programmieren, dass HTTP Basic Authentication prüft. Alternativ kann man auch das login.xml durch .htaccess schützen. Vorsicht: dabei nicht Zugriff auf die anderen Dateien verbieten. Man kann das login.xml dafür in ein anderes Verzeichnis legen und den PlatformLoginURL entsprechend setzen.

Für Fortgeschrittene:

Man kann den PlatformLoginURL auch in Firebat.sreg eintragen, wenn man mit leerer Registry startet. Registry überschreibt Einstellungen in Firebat.sreg.

Für Fortgeschrittene:

In identity2.xml ist auch ein Link auf eine Animationsdatei:
die auf die GIFs mit den Animationen verweist. Dann hat der Weblin auch Aktionen und bewegt sich.

Notwendig ist mindestens:
<config xmlns="http://schema.bluehands.de/character-config" version="1.0">
<sequence group="idle" name="idle1" probability="1000" type="status" in="standard" out="standard">
<animation src="still.gif" />
<sequence group="moveleft" name="moveleft1" probability="1000" type="basic" in="moveleft" out="moveleft">
<animation dx="-100" src="left.gif" />
<sequence group="moveright" name="moveright1" probability="1000" type="basic" in="moveright" out="moveright">
<animation dx="100" src="right.gif" />
Zum Start am Besten dieses kopieren und nur eigene GIFs verwenden. Das XML und die GIFs werden im Client gecached. Wenn man was ändert und die Änderung sehen will, dann Client Cache löschen (C:\Dokumente und Einstellungen\%USERNAME%\Anwendungsdaten\zweitgeist\cache).

Format in Kürze:
  • <config> hat mehrere <sequence>
  • <sequence> hat ein <animation>
  • <sequence> hat Attribut "group". Das ist die Aktion, laufen, stehen, schlafen, winken. Es kann mehrer mit gleicher "group" geben, dann sucht Weblin eine der <sequence>s zufällig aus gesteuert durch die relative Häufigkeit ("probability").
  • <sequence> hat Attribut "name". Muss nur eindeutig sei, aber beliebig.
  • <sequence> hat Attribut "probability": Relative Häufigkeit der <sequence> innerhalb einer "group"
  • <sequence> hat Attribut "type" mit Werten "status", "basic", "emote". "emote" tauchen als Aktionen im Avatar-Menu auf.
  • <sequence> Attribute "in", "out" kann man mal ignorieren
  • <animation> hat nur das Attribut "src". GIF URL absolut oder relative zu der XML Datei (mit oder ohne http://...)


Liebe Dunkle Seite der Macht: Das sind alles öffentliche Informationen, bzw welche, die schon vor 2006 bekannt waren. Keine Chance Ärger zu machen. Weblin ist einfach so gut programmiert, dass sowas geht. Vielen Dank auch an das Programmierteam, dass es trotz neuem Accountwizard so geblieben ist.