Giter VIP home page Giter VIP logo

habboapi's Introduction

Build status Latest Stable Version

HabboAPI

This PHP wrapper library is used to collect data from the undocumented Habbo API.
The project requires PHP 8.1 or higher and uses the Composer autoloader and PSR-4 standard.

Older versions for PHP 7.4 are available at Packagist.

See the example.php file on how you could use this library.

How to use it

  1. Add the Composer package to your project by running composer require gerbenjacobs/habbo-api
  2. On the page you want to use it add include 'vendor/autoload.php'
  3. Create a HabboParser and construct it with the Habbo domain extension "com", "com.br", "de" etc.
  4. Create a HabboAPI instance and inject the HabboParser in the constructor

Usage

    <?php
    // Include the Composer autoloader
    include 'vendor/autoload.php';
    
    // Shortcut for the FQN
    use HabboAPI\HabboAPI;
    use HabboAPI\HabboParser;
    
    // Create new Parser and API instance
    $habboParser = new HabboParser('com');
    $habboApi = new HabboAPI($habboParser);
    
    // Find the user 'koeientemmer' and get their ID
    $koeientemmer = $habboApi->getHabbo('koeientemmer')->getId();
    
    // Collect all the profile info
    $profile = $habboApi->getProfile($koeientemmer);

Changelog

  • April 30th, 2023 v6.0.0 - Fully support PHP 8.1 and up
  • December 29th, 2020 v5.0.0 - Add support for PHP 8 and drop support below PHP 7.3
  • December 18th, 2020 v4.1.0 - Adds "sandbox" as hotel, includes new values for Habbo entity; online, lastAccessTime, currentLevel, currentLevelCompleted, totalExperience, starGemCount
  • March 30th, 2020 v4.0.0 - Use Carbon 2.0 and drop support for PHP below 7.1.8
  • June 11th, 2018 v3.0.1 - Removed unused cookie logic
  • May 25th, 2018 - v3.0.0 - Removed official support for PHP 5.4, updated dependencies, fixed warnings for PHP 7.1
  • November 9th, 2017 - v2.4.0 - Added getGroupId() to Room entities, but only if that data exists
  • February 1st, 2017 - v2.3.0 - Added getAchievements() to API, returns a list of a Habbos achievements including current level and score
  • April 4th, 2016 - v2.2.0 - Added better exception handling, you can now catch MaintenanceException, HabboNotFoundException and UserInvalidException
  • March 17th, 2016 - v2.1.1 - Add/fix support for id and uniqueId in Room objects
  • February 25th, 2016 - v2.1.0 - Added getGroup and group member functionality
  • February 10th, 2016 - v2.0.2 - Changed cookie for JS detection
  • December 26th, 2015 - v2.0.1 - Fix for the cookie needed for Photos
  • December 10th, 2015 - v2.0.0 - Added Photos to API and implemented a Profile entity (Release notes)
  • December 4th, 2015 - v1.0.7 - Adds new attributes to Room entity
  • November 30th, 2015 - v1.0.6 - Small fixes to Room entity and better exception handling.
  • October 27th, 2015 - v1.0.5 - Allow parseHabbo() to use either Habboname or HHID. Also adds some stability to the Group entity
  • October 25th, 2015 - v1.0.3 - Throws exception if Habbo API replies with error and removed the HabboAPI directory for idiomatic packagist standards.
  • October 12th, 2015 - v1.0.2 - Removed server IP, upgraded PHPUnit and tests, expanded on example.php
  • March 30th, 2015 - v1.0.1 - Added hasProfile and more stable example.php
  • March 28th, 2015 - v1.0.0 - Created first tagged release, includes Travis CI and Packagist integration.

Developer Installation

  1. Clone the project
  2. Run composer install
  3. Verify the installation by running vendor/bin/phpunit or opening the example.php page on a PHP server

habboapi's People

Contributors

alikdb avatar dependabot[bot] avatar gerbenjacobs avatar lessevv avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

habboapi's Issues

Accept Turkish User names

Hey,

accept this API not name from Turkish users? I have a User and he can’t register by my site.

When a new user register on my site, he must write a Code in his mission. The api Check the mission from the user. Have the user the code in his mission, he can register by the site.

The api answer, he can’t find the user...

Name from the User: bektaş55

Create Profile entity

Will force the expected output on the getProfile() method instead of relying on some kind of array.

Furni data

Furni data

Maybe add furni data to the API?
I have some ideas for that and like to program that ;)

Data Mapper

Maybe an idea to create a data mapper in V2. So you can get arrays of everything. Like:

private function dataMapper(Habbo $obj)
{
    $data[ 'id' ]                = $obj->getId();
    $data[ 'habboName' ]         = $obj->getHabboName();
    $data[ 'motto' ]             = $obj->getMotto();
    $data[ 'memberSince' ]       = $obj->getMemberSince();
    $data[ 'figureString' ]      = $obj->getFigureString();
    $data[ 'profileVisible' ]    = $obj->getProfileVisible();
    return $data;
}

Btw this make it a lot easier to store data in a database.

So that you can access via an object function and array.
To come back on the remove of arrays in #2. Is this a better solution on #12.

example for my suggestion:

$habboObj = $API->getHabbo($id, 'object');
$habboData = $API->getHabbo($id, 'array');

$habboObj->getName();
$habboData['habboName'];

So you give default an object, else the format that is given. Like array

HabboParser('de')?

Hey! When I edit the HabboParser to the German Habbo, I have a blank website?

Code:
$habboParser = new HabboParser('de');

The Habboname exists.. Can anyone help?

API for Rooms

There should be a getRoom() endpoint that requires a unique room ID (r-hhus-dcf2e2a74a39f64dc627c5ef32a1aa83).

This won't give new info that's not in Profile, but it can be used to look up group rooms as mentioned in #28.

Investigate Habbo X hotel

Context

There's a new hotel that requires some kind of NFT token to access (I think?): https://nft.habbo.com/

However, it doesn't come with web profiles, but parts of the API still work.

https://www.habbox.game/api/public/users?name=Macklebee

Fetching the profile however doesn't work, as this results in user.invalid_id.

https://www.habbox.game/api/public/users/hhxp-b8725673423ff52c736ecc68800fbfe1/profile

Investigate

Let's see if they start to implement more of the profiles and if so, we can update the Habbo API to also deal with this 'hotel'.

Group Badge Hash

I'm wondering what the hash / algortihm / method or something is for the group badges

If you change something small on the group badge most of the badge hash changes, so can someone find out what the hash is?

Adjust API URL to be broader

At the moment the url has to point to: https://www.habbo.com/api/public/
This does not account for the extradata endpoint, which is a requirement for the Photos story (#3)

There's also two alternatives:

  • Only use "com", "nl", "de", "com.br".
  • Not having to supply the URL, but supply the hotel during querying (which will use &site in the final URL)

Way to check Habbo is banned

I have found a way to check if a Habbo is banned. You need to be authenticated, but that is hardly possible.

We use in this case:
  • Habbo.com
  • habboName: chinese
Normal API request:

If you get an habbo like: https://www.habbo.com/api/public/users?name=chinese the response will be: {"error":"not-found"}.
This response says three things banned, not exists.

Auth request:

If you are logged in to habbo create an new avatar with this name. Javascript calls this url: https://www.habbo.com/api/user/avatars/check-name?name=chinese
This request will return in this case {"isAvailable":false} (or in other cases: {"isAvailable":true} or {"message":"authentication-needed"})

conclusion:
Normal API request Auth request Conclusion
{"error":"not-found"} {"isAvailable":false} banned, Habbo refuse to give data. But the habboName is in use.
{"error":"not-found"} {"isAvailable":true} habboName not exists, Habbo hasn't data. And the habboName is free.

I have tried to get authenticated, but Googles new recaptcha is trolling/blocking me..

DON'T USE YOUR OWN ACCOUNT!!!
But no security risk here, if you use an demo mail address with no avatars and stuff on it. Something like [email protected] with password like abcdef1.

So I think this is impossible to check. But maybe you have an idea about this? 😄

Update Carbon and/or allow newer versions

At the moment HabboAPI uses Carbon 1.2 but the latest version is 2.6. We need to investigate whether we can add 2.6 as is, or if we have breaking changes. If so, update Carbon to 2.6

getThumbnailOrDefault

Apparently the Room entity has two images; ThumbnailUrl (occasionally works if set by a Habbo) and ImageUrl (not working at the moment, it appears)

So perhaps add a little helper function to the Room entity that you can query and give a default 'backup' image. It will check the URL and return the URL if valid or it will return the fallback URL.

$room = new Room();
$room->parse($room_data);

echo '<img src="' . $room->getThumbnailOrDefault('/images/default_room.png') . '" alt="My room">';

Habbo Mall API

Suggestion:

With the information below, my suggestion to create an parser for the endpoint Habbo Mall. With the data per country you can calculate the average price for one credit.
This information can be used by furni information. If you know that a rubber duck cost 1 credit you can also show the real value in currency. 1 credit is like ($0.10, £0.07, €0,09)
With this endpoint supported you can also show the latest deals in habbo like BUNDLE.

Technical information:

https://www.habbo.com/shopapi/public/countries
https://www.habbo.de/shopapi/public/countries
https://www.habbo.fr/shopapi/public/countries
https://www.habbo.fi/shopapi/public/countries
https://www.habbo.it/shopapi/public/countries
https://www.habbo.com.tr/shopapi/public/countries
https://www.habbo.nl/shopapi/public/countries
https://www.habbo.es/shopapi/public/countries
https://www.habbo.com.br/shopapi/public/countries
Give some results like this:

[{
    "id": 5103,
    "name": "Australia",
    "locale": null,
    "countryCode": "au"
}, {
    "id": 5115,
    "name": "Brazil",
    "locale": null,
    "countryCode": "br"
}, ...]

With the results above you can get: (strict by hotel domain extension!!!)
https://www.habbo.com/shopapi/public/inventory/au
https://www.habbo.com/shopapi/public/inventory/br
https://www.habbo.com/shopapi/public/inventory/ca
https://www.habbo.com/shopapi/public/inventory/hk
and
https://www.habbo.nl/shopapi/public/inventory/nl
https://www.habbo.nl/shopapi/public/inventory/be
ect..
Will give you results like this:

{
    "country": {
        "id": 5115,
        "name": "Brazil",
        "locale": null,
        "countryCode": "br"
    },
    "paymentCategories": ["online"],
    "pricePoints": [{
        "id": 9594,
        "name": "6-months Habbo Club",
        "description": "Enjoy a whopping 6 MONTHS of HC membership exclusives with this offer! Still have some days left on your membership? The new days will just be added on top - you'll never pay double.",
        "creditAmount": 0,
        "price": "R$ 30,00",
        "iconId": 5,
        "categories": ["HABBO_CLUB"],
        "countryCode": "br",
        "paymentMethods": [{
            "id": 19149,
            "name": "Paymentez BR direct",
            "smallPrint": null,
            "buttonText": null,
            "category": "online",
            "buttonLogoUrl": "//habboo-a.akamaihd.net/c_images/cbs2_partner_logos/logoPaymentez_final_2_0_white.png",
            "localizationKey": "paymentez_br_direct",
            "disclaimerRequired": false,
            "premiumSms": false,
            "requestPath": "online",
            "purchaseParams": {
                "countryId": 5115,
                "pricePointId": 9594,
                "paymentMethodId": 19149
            }
        }]
    }, {
        "id": 9615,
        "name": "1-year Habbo Club",
        "description": "An immense 1 YEAR of HC membership exclusives! Still have some days left on your membership? The new days will just be added on top - you'll never pay double.",
        "creditAmount": 0,
        "price": "R$ 54,00",
        "iconId": 5,
        "categories": ["HABBO_CLUB"],
        "countryCode": "br",
        "paymentMethods": [{
            "id": 19149,
            "name": "Paymentez BR direct",
            "smallPrint": null,
            "buttonText": null,
            "category": "online",
            "buttonLogoUrl": "//habboo-a.akamaihd.net/c_images/cbs2_partner_logos/logoPaymentez_final_2_0_white.png",
            "localizationKey": "paymentez_br_direct",
            "disclaimerRequired": false,
            "premiumSms": false,
            "requestPath": "online",
            "purchaseParams": {
                "countryId": 5115,
                "pricePointId": 9615,
                "paymentMethodId": 19149
            }
        }]
    }, {
        "id": 9480,
        "creditAmount": 55,
        "price": "R$ 16,00",
        "iconId": 7,
        "categories": ["CREDITS"],
        "countryCode": "br",
        "paymentMethods": [{
            "id": 19149,
            "name": "Paymentez BR direct",
            "smallPrint": null,
            "buttonText": null,
            "category": "online",
            "buttonLogoUrl": "//habboo-a.akamaihd.net/c_images/cbs2_partner_logos/logoPaymentez_final_2_0_white.png",
            "localizationKey": "paymentez_br_direct",
            "disclaimerRequired": false,
            "premiumSms": false,
            "requestPath": "online",
            "purchaseParams": {
                "countryId": 5115,
                "pricePointId": 9480,
                "paymentMethodId": 19149
            }
        }],
        "doubleCredits": false
    }, {
        "id": 9481,
        "creditAmount": 110,
        "price": "R$ 30,00",
        "iconId": 7,
        "categories": ["CREDITS"],
        "countryCode": "br",
        "paymentMethods": [{
            "id": 19149,
            "name": "Paymentez BR direct",
            "smallPrint": null,
            "buttonText": null,
            "category": "online",
            "buttonLogoUrl": "//habboo-a.akamaihd.net/c_images/cbs2_partner_logos/logoPaymentez_final_2_0_white.png",
            "localizationKey": "paymentez_br_direct",
            "disclaimerRequired": false,
            "premiumSms": false,
            "requestPath": "online",
            "purchaseParams": {
                "countryId": 5115,
                "pricePointId": 9481,
                "paymentMethodId": 19149
            }
        }],
        "doubleCredits": false
    }, {
        "id": 9482,
        "creditAmount": 225,
        "price": "R$ 60,00",
        "iconId": 7,
        "categories": ["CREDITS"],
        "countryCode": "br",
        "paymentMethods": [{
            "id": 19149,
            "name": "Paymentez BR direct",
            "smallPrint": null,
            "buttonText": null,
            "category": "online",
            "buttonLogoUrl": "//habboo-a.akamaihd.net/c_images/cbs2_partner_logos/logoPaymentez_final_2_0_white.png",
            "localizationKey": "paymentez_br_direct",
            "disclaimerRequired": false,
            "premiumSms": false,
            "requestPath": "online",
            "purchaseParams": {
                "countryId": 5115,
                "pricePointId": 9482,
                "paymentMethodId": 19149
            }
        }],
        "doubleCredits": false
    }, {
        "id": 9483,
        "creditAmount": 590,
        "price": "R$ 150,00",
        "iconId": 7,
        "categories": ["CREDITS"],
        "countryCode": "br",
        "paymentMethods": [{
            "id": 19149,
            "name": "Paymentez BR direct",
            "smallPrint": null,
            "buttonText": null,
            "category": "online",
            "buttonLogoUrl": "//habboo-a.akamaihd.net/c_images/cbs2_partner_logos/logoPaymentez_final_2_0_white.png",
            "localizationKey": "paymentez_br_direct",
            "disclaimerRequired": false,
            "premiumSms": false,
            "requestPath": "online",
            "purchaseParams": {
                "countryId": 5115,
                "pricePointId": 9483,
                "paymentMethodId": 19149
            }
        }],
        "doubleCredits": false
    }],
    "doubleCredits": false
}

You have different categories in the Habbo Mall:
These categories can be combined with other categories like HABBO_CLUB + BUILDERS_CLUB.

  • HABBO_CLUB
  • BUILDERS_CLUB
  • CREDITS
  • BUNDLE
  • Maybe some more.

Members of a group

list($member_data) = $this->_callUrl($this->api_base . '/api/public/groups/' . $group_id . '/members', true);

I'm not sure Habbo gives the possibility to extract members of a group in their API. Where have you found this ?

Dissect clothes and colours from figurestring

It would be nice if we could make a static resource of the clothing items and their colours from the figurestring 'codes'.

sh-905-1408 means shoes of type '905' with colour '1408'. If this could become:

{
  'item': 'shoes',
  'name': 'sneakers',
  'colour': '#ff00bb'
}

One of the ideas is that you can then start searching for people who are wearing a special kind of shirt.

Some hotels are blocked by DOSarrest

The hotels com.br, com and es are behind DOSarrest protection and require a cookie to be set by Javascript, to verify you're a 'normal' visitor. Without the correct 'token' the API calls result in HTTP 500 errors.

The code already contained code for something similar but this stopped working and needs investigation.

Getters

Question:

Lately in Laravel I have seen a lot of people use the variable name as getter, without the Java-esque way of camelcasing.

What should we use in v2?

// Group entity

// #1
function getDescription() {}

// #2
function description() {}

// #3 - no getter at all
public $description;

// #4 magic __get
function __get() {
  // if get$name() exists use that
  // else if theres a property use that
  // else error; not found
}

This would translate in output as follows:

// Output

// #1
echo 'Room: ' . $room->getDescription() . ' - Visit now!';

// #2
echo 'Room: ' . $room->description() . ' - Visit now!';

// #3 & #4
echo 'Room: ' . $room->description . ' - Visit now!';

Sulake API wishlist

Since Macklebee (Sulake staff) is intending to enhance the Habbo API and asked us for some ideas, I thought it would be a good idea to list them here. If you have other ideas, please post them as a comment and I'll update this main post.

Achievements

  • The 'achievement score' that Habbo generates
  • All achievements and their progression towards the next rank
    • Would be nice if this contained hard numbers i.e. "snap 26 more photos", "have 13 more friends"; but a percentage based number would suffice

Habbo/Profile

  • Return the users that are assigned to the love/hate/smiley thing
  • Whether a user is online or offline
  • List of purchased clothes

Rooms

  • The name of the wallpaper / flooring used
  • A list of furniture inside or (in case that exposes a security risk) a simple count of wall items vs floor items
  • A list of current users

Marketplace

  • All the data, basically 😜
  • Count of each furni item in circulation (Perhaps including some kind of 'last updated' timestamp, so we can exclude items that are in the 'storage room' of someone who left or is blocked)

Groups

  • Add group owner (just like ownerUniqueId in Rooms)

Timestamps

  • When did a Habbo get a badge, or a friend, or joined a group. If this is not privacy sensitive, it would be nice to have.

OAuth API / Write access

So now for something completely different; How about an actual API that lets you give out 'write access'. Things that can then become available:

  • Auto-purchasing / selling of items on the marketplace
  • Sending gifts or furni to friends (ideal for fansite giveaways)
  • Changing your look (ideal for armies/police/hospital roleplaying)
  • Creating a room based on a blueprint with or without inventory
  • Changing text on Wired blocks (ideal for fansites too)

For example, the combination of Changing looks and Changing Wired texts could give fansites the ability to run games that rely on more than wired conditions, i.e. data from their own site. You could run games like Mafia/Werewolf or elaborated Pub Quizzes with just a couple of Wired blocks.

Custom message - throw new Exception (HabboParser.php)(dutch)

Misschien de error code en error status terug geven in HabboParser.php?
In plaats van de zin Habbo API replied with an error code:

Reden:

Zo kan iemand makkelijk op basis van error code of error status een eigen custom error melding maken. Zoals:

  • 400: Bad Request
  • 404: Habbo niet gevonden.
Oud:
if ($data['habboAPI_info']['http_code'] != 200)
{
throw new Exception('Habbo API replied with an error code: ' . $data['error']);
}
Suggestie:
if ($data['habboAPI_info']['http_code'] != 200)
{
throw new Exception($data['error'], $data['habboAPI_info']['http_code']);
}

Of als je een beter idee hebt dat dit, zoals een message bag of iets dergelijks mag natuurlijk ook ;)

Create APIs for Groups

https://www.habbo.com/api/public/groups/g-hhus-fd92759bc932225f663f1521be8ce255

{
    "id": "g-hhus-fd92759bc932225f663f1521be8ce255",
    "name": "UK",
    "description": "",
    "type": "NORMAL",
    "roomId": "r-hhus-dcf2e2a74a39f64dc627c5ef32a1aa83",
    "badgeCode": "b22114s19107t50064e4bcf7c532fdcecc1467d2185d975783",
    "primaryColour": "ffd601",
    "secondaryColour": "ffffff"
}

https://www.habbo.com/api/public/groups/g-hhus-fd92759bc932225f663f1521be8ce255/members

[{
    "gender": "f",
    "motto": "",
    "habboFigure": "hr-9534-45.hd-600-1.ch-3013-92.lg-3216-1332.sh-3064-1332.cc-3008-92-1332.cp-3128-62",
    "memberSince": "2005-01-05T08:21:03.000+0000",
    "uniqueId": "hhus-3037442649c23d0ab7c9c059cbf91981",
    "name": "aiko05$",
    "isAdmin": false
}, {
    "gender": "m",
    "motto": "hello!",
    "habboFigure": "hr-125-45.hd-180-1.ch-255-82.lg-3023-88.sh-300-91",
    "memberSince": "2005-11-25T23:34:19.000+0000",
    "uniqueId": "hhus-0b2bde97bcf11e57e71f0fe9ad963574",
    "name": "Jyoko",
    "isAdmin": false
}, {
    "gender": "m",
    "motto": "I'm Anthony ƒ Add etc NEW ACCOUNT",
    "habboFigure": "hr-170-45.hd-190-1.ch-255-78.lg-275-82.sh-290-62.ha-1004-91",
    "memberSince": "2006-04-03T17:02:12.000+0000",
    "uniqueId": "hhus-62e9cb80440a5ac20fab88b452c94a42",
    "name": "Mirokujr",
    "isAdmin": false
}, {
    "gender": "m",
    "motto": "",
    "habboFigure": "hr-891-42.hd-180-1.ch-3109-72-62.lg-281-72.sh-906-72.ha-1003-64.ea-3169-100.fa-1201.cp-3120-62",
    "memberSince": "2006-05-21T20:32:58.000+0000",
    "uniqueId": "hhus-bb7b09144f28973e9f67a4870de45ec8",
    "name": "GPN$",
    "isAdmin": false
}]
...

I did find out that certains groups return error: maintenance, but maybe it's because they are too big and/or have the status of Unlimited.

FigureString Gender

This is not an issue with the HabboAPI PHP Wrapper, but this place just seems to be full of intelligent people so I might just ask here:

Are you able to extract the gender from a FigureString?

DOSArrest is blocking API calls to Habbo.nl

This issue first occured when putting my fansite out there and it threw a HTTP 500 error when calling the Habbo API. Upon further investigation, it turned out DOSArrest is blocking my requests with a HTTP 403 error.

My question is: How do I fix this? I think it's got something to do with cookies. Thanks in advance.

Special character problem in username

I need motto authentication to register users on fansite and I'm having trouble registering users with special characters (ö, ş, ğ, ı, etc.), looking for a solution.

HabboParser.php error

Hey! I have a error....

ErrorException in HabboParser.php line 88:
Undefined index: error (View: /var/www/html/resources/views/page/startseite/startsite.blade.php)

Can anyone help me? Thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.