Contents
Developers Guide
Information for Hubzilla developers
Who is a Hubzilla developer? Should I be reading this?
Anyone who contributes to making Hubzilla better is a developer. There are many different and important ways you can contribute to this amazing technology, even if you don't know how to write code. The software itself is only one part of the Hubzilla project. You can contribute by
- translating the text into your language so that people all over the world have the opportunity to use Hubzilla
- Promote Hubzilla and spread awareness of the platform through blog posts, articles and word of mouth
- Create artwork and graphics for project resources such as icons and marketing materials
- Supporting the project infrastructure such as the project website and demo servers
Software developers are of course welcome; there are so many great ideas that can be realised and not enough people to make them all happen! The Hubzilla codebase is an advanced and mature system, but the platform is still very flexible and open to new ideas.
We are pretty relaxed when it comes to developers. We don't have a lot of rules. Some of us are overworked, and if you want to help, we're happy to let you help. However, if you follow a few guidelines, the process will be smoother and collaboration easier. All developers are expected to adhere to our code of conduct. We have developers from all over the world with different skills, different cultural backgrounds and different levels of patience. Our number one rule is to respect others. Sometimes this is difficult, and sometimes we have very different views on how things should work, but if everyone makes an effort, we will get along well.
This document will help you get to know and help shape Hubzilla.
You want to contribute code?
...and don't know how to start to...
- debug hubzilla (php on the webserver),
- contribute code to the project,
- optionally - do it all from inside a virtual machine
This manual was tested for Debian (Wheezy) as virtual machine on Lubuntu (Ubuntu 14.0) as host.
Install a Virtual Machine (KVM)
Here the installation guide for Linux Debian. The summary:
- install KVM
# apt-get install qemu-kvm libvirt-bin
- add yourself to the group libvirt
# adduser <youruser> libvirt
- install gui to manage virtual machines
# apt-get install virt-manager
- download an operating system to run inside the vm (mini.iso)
- start the virt manager
- create new virtual machine (click on icon) - choose your iso image (just downloaded) as installation source - optional: configure the new vm: ram, cpu's,... - start virtual machine > result: linux debian starts in a new window. 6. (optional) avoid network errors after restart of host os
# virsh net-start default
# virsh net-autostart default
Install Apache Webserver
Open a terminal and make yourself root
su -l
Create the standard group for the Apache webserver
groupadd www-data
might exist already
usermod -a -G www-data www-data
Check if the system is really up to date
apt-get update
apt-get upgrade
Optional restart services after installation
reboot
If you restarted, make yourself root
su -l
Install Apache:
apt-get install apache2 apache2-doc apache2-utils
Open webbrowser on PC and check localhost Should show you a page like "It works"
(Source http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#)
Install PHP, MySQL, phpMyAdmin
PHP, MySQL
su -l
apt-get install libapache2-mod-php8.2 php8.2 php-pear php8.2-xcache php8.2-curl php8.2-mcrypt php8.2-xdebug
apt-get install php8.2-mysql
apt-get install mysql-server mysql-client
enter and note the mysql passwort
Optional since its already enabled during phpmyadmin setup
phpenmod mcrypt
phpMyAdmin
Install php myadmin
apt-get install phpmyadmin
Configuring phpmyadmin - Select apache2 (hint: use the tab key to select) - Configure database for phpmyadmin with dbconfig-common?: Choose Yes
(Source http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#)
Enable rewrite
The default installation of Apache2 comes with mod_rewrite installed. To check whether this is the case, verify the existence of /etc/apache2/mods-available/rewrite.load
nano /etc/apache2/mods-available/rewrite.load
(You should find the content: LoadModule rewrite_module
/usr/lib/apache2/modules/mod_rewrite.so
)
To enable and load mod_rewrite, do the rest of steps.
Create a symbolic link in /etc/apache2/mods-enabled
cd /var/www
root@debian /var/www $ a2enmod rewrite
Then open up the following file, and replace every occurrence of "AllowOverride None
" with "AllowOverride all
".
nano /etc/apache2/apache2.conf
or
gedit /etc/apache2/sites-enabled/000-default
Finally, restart Apache2.
service apache2 restart
Test installation
cd /var/www
create a php file to test the php installation
nano phpinfo.php
Insert into the file:
<?php
phpinfo();
?>
(save CTRL+0, ENTER, CTRL+X)
open webbrowser on PC and try http://localhost/phpinfo.php
(page shows infos on php)
connect phpMyAdmin with MySQL database
nano /etc/apache2/apache2.conf
- CTRL+V... to the end of the file
- Insert at the end of the file: (save CTRL+0, ENTER, CTRL+X)
Include /etc/phpmyadmin/apache.conf
restart apache
/etc/init.d/apache2 restart
apt-get update
apt-get upgrade
reboot
phpMyAdmin
open webbrowser on PC and try http://localhost/phpmyadmin
(Source http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#)
Create an empty database... that is later used by hubzilla
open webbrowser on PC and try http://localhost/phpmyadmin
Create an empty database, for example named "red". Create a database user, for example "red". Grant all rights for the user "red" to the database "red".
Note the access details (hostname, username, password, database name).
Fork the project on github
Please follow the instruction in the offiical documentation of git. It is a good idea to read the whole manual! Git is different to other version control systems in many ways.
Now you should
- create an account at github.com
- fork
https://framagit.org/hubzilla/core
- fork
https://framagit.org/hubzilla/addons
If you not want to use GIT from the command line - there is a usefull Eclipse plugin named ""Eclipse Mylyn to GitHub connector".
Install Hubzilla and its Addons
Git at your computer / vm
You should have created an account on github and forked the projects befor you procceed.
Delete the directory www
rm -R www/
Install git (and optionally git-gui a client gui)
apt-get install git git-gui
Download hubzilla and addons
Download the main project hubzilla and hubzilla-addons
git clone https://github.com/yourname/hubzilla www
cd www/
git clone https://github.com/yourname/hubzilla-addons addon
Make this extra folder
mkdir -p "store/[data]/smarty3"
Create .htconfig.php and make it writable by the webserver
touch .htconfig.php
chmod ou+w .htconfig.php
Make user www-data (webserver) is the owner all the project files
cd ..
chown -R www-data:www-data www/
Add yourself ("surfer" in this example) to the group www-data. Why? Later you want to modify files in eclipse or in another editor.
Then make all files writable by the group www-date you are now a member of.
cd www/
usermod -G www-data surfer
chmod -R g+w www/
Restart the computer (or vm)
If you are still not able to modify the project files you can check the members of the group www-data with
cat /etc/group
Register yourself as admin
Open http://localhost
and init the matrix
Befor you register a first user switch off the registration mails.
Open /var/www/.htconfig.php
and make sure "0" is set in this line
App::$config['system']['verify_email'] = 0;
You should be able to change the file as "yourself" (instead of using root or www-data).
Cron and the poller
Important! Run the poller to pick up the recent "public" postings of your friends Set up a cron job or scheduled task to run the poller once every 5-10 minutes to pick up the recent "public" postings of your friends
crontab -e
Add
*/10 * * * * cd /var/www/; /usr/bin/php include/poller.php
If you don't know the path to PHP type
which php
Debug the server via eclipse
Check the configuration of xdebug
You shoud already have installed xdebug in the steps befor
apt-get install php5-xdebug
Configuring Xdebug
Open your terminal and type as root (su -l)
gedit /etc/php5/mods-available/xdebug.ini
if the file is empty try this location
gedit /etc/php5/conf.d/xdebug.ini
That command should open the text editor gedit with the Xdebug configuration file At the end of the file content append the following text
xdebug.remote_enable=on
xdebug.remote_handler=dbgp
xdebug.remote_host=localhost
xdebug.remote_port=9000
Save changes and close the editor. In you terminal type to restart the web server.
service apache2 restart
Install Eclipse and start debugging
Install eclipse. Start eclipse with default worspace (or as you like)
Install the PHP plugin Menu > Help > Install new software... Install "PHP Developnent Tools ..."
Optionally - Install the GitHub connector plugin Menu > Help > Install new software... Install "Eclipse Mylyn to GitHub connector"
Configure the PHP plugin Menu > Window > Preferences...
General > Webbrowser > Change to "Use external web browser" PHP > Debug > Debug Settings > PHP Debugger > Change to "XDebug"
Create a new PHP project Menu > File > New Project > Choose PHP > "PHP Project"
Choose Create project at existing location" and "/var/www"
Start debugging
Open index.php and "Debug as..."
Choose as Launch URL: "http://localhost/
"
Expected:
- The web browser starts
- The debugger will stop at the first php line
Contribute your changes via github
Preparations
There is a related page in this docs: [zrl=[baseurl]/help/git_for_non_developers]Git for Non-Developers[/zrl]. As stated befor it is recommended to read the official documentation GitHub-Contributing-to-a-Project of git.
Eclipse has a usefull plugin for GIT: "Eclipse Mylyn to GitHub connector".
Make sure you have set your data
git config --global user.name "Your Name"
git config --global user.email "your@mail.com"
Your first contribution
Create a descriptive topic branch
git checkout -b dev_beginning
Make sure your local repository is up-to-date with the main project. Add the original repository as a remote named “upstream” if not done yet
git remote add upstream https://framagit.org/hubzilla/core/
Fetch the newest work from that remote
git fetch upstream
git merge upstream/master
Hint: You can list the branches
git branch -v
Make your changes. In this example it is a new doc file.
Check your modifications
git status
Add (stage) the new file
git add doc/dev_beginner.bb
Commit the changes to your local branch. This will open an editor to provide a message.
git commit -a
Push back up to the same topic branch online
git push
Now you can go to your (online) account at github and create the pull request.
Following contributions
In case the main devolpers want you to change something. Fetch the newest work from the remote upstream/master to be sure you have the latest changes.
git fetch upstream
git merge upstream/master
Make your changes, test them, commit (to local repository), push (to online repository)
git status
git commit -a -m "added modification of branch"
git push
Versions and releases
Hubzilla currently uses a standard version numbering sequence of $x.$y(.$z), for example ‘1.12’ or ‘1.12.1’. The first digit is the major version number. Major versions are released ‘approximately’ once a year, often in December.
The second digit is the minor version number. If this number is odd, it is a development version. If the number is even, it is a released version. Minor versions are usually released once a month (moved from the development version to the major version) when development is ‘stable’, but this is likely to increase. In the future, minor releases will be released between one and three months; this corresponds to a stable code point and when there is a general consensus in the community that the current code base is stable enough to consider a release.
The last digit is an interface or patch identifier.
The release process involves changing the version number (by definition, the minor version number is odd, and the minor number is incremented). Once a year, the major version number of a major version is incremented and the minor version number is reset to 0.
The version candidate is moved to a new branch and testing begins or continues for a period of 1-2 weeks, or until all major issues have been resolved. This branch is usually labelled RC (Release Candidate); for example, 1.8RC stands for the upcoming release of version 1.8. At this point, the version number of the development branch is increased to the next odd number. (For example 1.9). New developments can then take place in the development branch.
Bug fixes should always be applied to ‘dev’ and transferred from there (usually with git cherry-pick) to the RC branch and, if necessary, to the master or official release branch.
At the time a release candidate is created, the language string file is frozen until a new version is released. Translation work can continue, but all translations should be submitted to ‘dev’ and merged into RC. Once RC testing is complete, RC will be merged into ‘master’ and the RC version identifier removed; this will result in a final checkin to change the version number. The CHANGELOG file should also be updated at this point or shortly before. If there are conflicts during this final merge, the merge is cancelled and ‘git merge -s ours’ is applied. This results in the master branch being replaced by the content of the RC branch. Conflicts often arise due to string updates that were made to master after the last release and cannot simply be resolved without manual editing. As this is a release of tested code, manual editing is not recommended and the replacement merge strategy should be used instead. It is assumed that RC now contains the latest, well-tested code.
Once the release is live and merged into the master branch, the RC branch can be removed.
After the release, corrections can be made to master. If possible, these should be made in dev and ‘git cherry-pick’ should be used to merge; this preserves the commit information and avoids conflicts when merging in the next cycle. Only rarely does a patch only apply to the master branch. If necessary, this can be done. If the change is major, the version number of the interface should be increased. This is at the discretion of the community. In any case, a ‘git pull’ of the master branch should always result in the latest version, to which all patches are applied after release.
The interface number (the $z in $x.$y.$z) should be incremented in dev whenever a change is made that alters the interfaces or API in an incompatible way, so that any external packages (especially addons and API clients) that rely on the current behaviour can detect their own interfaces at the point where it has changed and change them accordingly.
Git repository branches
There are two official branches of the Hubzilla Git repository.
- The stable version is maintained in the master branch. The latest commit in this branch is considered suitable for production hubs.
- Experimental development takes place in the dev branch, which is transferred to the master branch as soon as it is deemed tested and stable enough.
Git For Non-Developers
So you're handling a translation, or you're contributing to a theme, and every time you make a pull request you have to talk to one of the developers before your changes can be merged in?
Chances are, you just haven't found a quick how-to explaining how to keep things in sync on your end. It's really very easy.
After you've created a fork of the repo (just click "fork" at github), you need to clone your own copy.
For the sake of examples, we'll assume you're working on a theme called redexample (which does not exist).
git clone https://github.com/username/red.git
Once you've done that, cd into the directory, and add an upstream.
cd red
git remote add upstream https://framagit.org/hubzilla/core/
From now on, you can pull upstream changes with the command
git fetch upstream
Before your changes can be merged automatically, you will often need to merge upstream changes.
git merge upstream/master
You should always merge upstream before pushing any changes, and must merge upstream with any pull requests to make them automatically mergeable.
99% of the time, this will all go well. The only time it won't is if somebody else has been editing the same files as you - and often, only if they have been editing the same lines of the same files. If that happens, that would be a good time to request help until you get the hang of handling your own merge conflicts.
Then you just need to add your changes
git add view/theme/redexample/
This will add all the files in view/theme/redexample and any subdirectories. If your particular files are mixed throughout the code, you should add one at a time. Try not to do git add -a, as this will add everything, including temporary files (we mostly, but not always catch those with a .gitignore) and any local changes you have, but did not intend to commit.
Once you have added all the files you have changed, you need to commit them.
git commit
This will open up an editor where you can describe the changes you have made. Save this file, and exit the editor.
Finally, push the changes to your own git
git push
And that's it, your repo is up to date!
All you need to do now is actually create the pull request. There are two ways to do this.
The easy way, if you're using Github is to simply click the green button at the top of your own copy of the repository, enter a description of the changes, and click 'create pull request'. The main repository, themes, and addons all have their main branch at Github, so this method can be used most of the time.
Most people can stop here.
Some projects in the extended RedMatrix ecosphere have no Github presence, to pull request these is a bit different - you'll have to create your pull request manually. Fortunately, this isn't much harder.
git request-pull -p <start> <url>
Start is the name of a commit to start at. This must exist upstream. Normally, you just want master.
URL is the URL of your repo.
One can also specify <end>
. This defaults to HEAD.
Example:
git request-pull master https://example.com/project
And simply send the output to the project maintainer.
Tools and workflows for developers
Hub snapshots
The hub snapshots page contains instructions and scripts for creating full snapshots of a hub to support switching between consistent and fully known states. This is useful to avoid situations where the content or database schema may be incompatible with the code.
Creating Documentation
To contribute documentation, simply put some words in a cunning order, and make their existence known to a developer. You can do this literally anywhere as long as a developer can see it. Once made aware, somebody will check it in for you. You should try to avoid proprietary formats, or locations that require authentication with methods other than Nomad in order to make it easy for a developer to access, but even this is not a strict requirement.
If you wish to contribute directly, that's fine too. To contribute directly, documentation should be in one of the following formats:
- Markdown
- BBCode
- HTML
- Plain Text
- Other formats are also allowed, but support for the format must be added to mod/help.php first.
If editing a plain text file, please keep column width to 80. This is because plain text is used in instances where we may not have a working installation - the installation documentation, for example - and it should be easy to read these from a CLI text editor.
The advantage of Markdown is that it is human readable.
The advantage of BBCode is that it is identity aware.
Therefore, if using BBCode, try to make the most of it:
- Use ZRL links where appropriate to ensure a link to another site retains authentication and keeps identity based documentation working
- Use baseurl or observer.baseurl tags where appropriate instead of example.com for authenticated viewers.
- Support non-authenticated users with observer=0 tags. We presently do not do this due to historical oversights. This needs adding everywhere
Translations
To translate documentation, or provided documentation in languages other than English:
- Create a directory in doc/ with your two letter country code if it doesn't already exist (eg, doc/de/ for German or doc/fr/ for French)
- Create a document with the same filename as the English version, but with content in your own language. This allows us to fallback to the English if the translation for a particular page is not provided
To create documentation that has no equivalent file in English, you can create a new file with a name of your choosing - but you'll also need to provide a localised version of the index page (main.bb in English) to make it accessible from the menu.
Translations
Our translations are managed through Transifex. If you would like to help translate Hubzilla into another language, sign up at transifex.com, visit Transifex and apply to join one of the existing language teams or create a new one. Notify one of the lead developers if you have a translation update that needs merging, or ask if you can merge it yourself if you are familiar with Git and PHP. We have a string file called ‘messages.po’ which is gettext compatible, and a handful of email templates from which we automatically generate the application language files.
The translation process
The strings used in Hubzilla's user interface are translated at Transifex and then added to the Git repository on github. If you would like to help translate a language, be it correcting terms or translating Hubzilla into a currently unsupported language, please register an account on transifex.com and contact the translation team there.
Translating Hubzilla is easy. Simply use the online tool on transifex. If you don't want to bother with Git & Co. it's no problem, we regularly check the status of the translations and import them into the source tree on github for others to use.
We do not include every translation of transifex in the source tree to avoid a scattered and disrupted overall experience. As a rough estimate, we have a lower limit of 50% translated strings before we include the language. This limit is only based on the amount of translated strings, assuming that the most important strings for the user interface are translated first by a translation team. If you feel that your translation is usable before this limit, please contact us and we will likely include your team's work in the source tree. If you would like to add your work to the source tree yourself, please feel free to do so and contact us with any questions that arise. The process is simple and Hubzilla comes with all the necessary tools.
The location of the translated files in the source tree is
/View/LNG-CODE/
where LNG-CODE is the code of the language used, e.g. de for German or fr for French. For the e-mail templates (the *.tpl files), simply place them in the directory and you're done. The translated strings come as an ‘hmessages.po’ file from Transifex, which needs to be translated into the PHP file that Hubzilla uses. To do this, place the file in the directory mentioned above and use the ‘po2php’ utility from the util directory of your Hubzilla installation.
Assuming you want to convert the German localisation, which is located in view/en/hmessages.po, you would proceed as follows.
- At the command prompt, navigate to the base directory of your
Hubzilla installation
- Execute the po2php script that translates the
into the hstrings.php file used by Hubzilla.
$> php util/po2php.php view/en/hmessages.po
The output of the script will be placed in the view/en/hstrings.php file where froemdoca expects it, so you can test your translation immediately.
- Visit your Hubzilla page and check that it is in the language you just translated.
language you have just translated. If not, try to find the error, probably PHP will give you a hint in the log/warnings.about the error.
For debugging you can also try to ‘execute’ the file with PHP. This should give no output if the file is ok, but may give you a hint for the error in the file.
$> php view/en/hstrings.php
- Commit the two files to your git repository with a meaningful commit message, push it to your fork of the Hubzilla repository on github and make a pull request for this commit.
Utilities
In addition to the po2php script, there are several other utilities for translation in the ‘util’ directory of the Hubzilla source tree. If you just want to translate Hubzilla into another language, you probably won't need any of these tools, but it will give you an idea of how Hubzilla's translation process works. See the utils/README file for more information.
Known issues
* Hubzilla uses the language setting of the visitor's browser to set the language for the user interface. Most of the time this works, but there are some known quirks. * the early translations are based on the friendica translations, if you find some rough translations please let us know or correct them at Transifex.
Licensing
All code contributed to the project is subject to the MIT licence unless otherwise stated. We accept third party code that falls under MIT, BSD and LGPL, but copyleft licences (GPL and AGPL) are only allowed in addons. It must be possible to completely remove the GPL (copyleft) code from the main project without destroying anything.
Coding style
In the interest of consistency, we use the following code style. We also accept patches that use other styles, but please try to use a consistent coding style if possible. We will not argue or discuss the merits of this style, and it is irrelevant what project ‘xyz’ uses. This is not project ‘xyz’. This is a baseline to try to keep the code readable now and in the future.
- All comments should be in English.
- We use Doxygen to create documentation. This has not been used consistently, but it is highly recommended to learn and use it.
- Indentation is mainly done by tabs with a tab width of 4.
- String concatenations and operators should be separated by spaces. e.g.
$foo = $bar . ‘abc’;
instead of$foo=$bar.”abc’;
- In general, we use single quotes for string variables and double quotes for SQL statements. ‘Here documents’ should be avoided. Sometimes using strings in double quotes with variable substitution is the most efficient way to create the string. In most cases, you should use single quotes.
- Use spaces liberally to improve readability. When creating arrays with many elements, a key/value pair is often set per line, which is indented accordingly from the parent line. Lining up the assignment operators requires a little more work, but also increases readability.
- In general, opening curly brackets are placed on the same line as what the bracket opens. They are the last character in the line. Closing brackets are placed on a separate line.
- Some functions take arguments in argc/argv style, such as main() in C or function args in Bash or Perl. Urls are split within a module. e.g. for
http://example.com/module/arg1/arg2
$this->argc is 3 (integer) and $this->argv contains: [0] => ‘module’, [1] => ‘arg1’, [2] => ‘arg2’. Only one argument is ever specified. If a naked domain URL is specified, $this->argv[0] is set to ‘home’.
Creating Plugins/Addons for Hubzilla
So you want to make hubzilla do something it doesn't already do. There are lots of ways. But let's learn how to write a plugin or addon.
In your hubzilla folder/directory, you will probably see a sub-directory called 'addon'. If you don't have one already, go ahead and create it.
mkdir addon
Then figure out a name for your addon. You probably have at least a vague idea of what you want it to do. For our example I'm going to create a plugin called 'randplace' that provides a somewhat random location for each of your posts. The name of your plugin is used to find the functions we need to access and is part of the function names, so to be safe, use only simple text characters.
Once you've chosen a name, create a directory beneath 'addon' to hold your working file or files.
mkdir addon/randplace
Now create your plugin file. It needs to have the same name, and it's a PHP script, so using your favourite editor, create the file
addon/randplace/randplace.php
The very first line of this file needs to be
<?php
Then we're going to create a comment block to describe the plugin. There's a special format for this. We use /* ... */ comment-style and some tagged lines consisting of
/**
*
* Name: Random Place (here you can use better descriptions than you could in the filename)
* Description: Sample hubzilla plugin, Sets a random place when posting.
* Version: 1.0
* Author: Mike Macgirvin <mike@zothub.com>
*
*/
These tags will be seen by the site administrator when he/she installs or manages plugins from the admin panel. There can be more than one author. Just add another line starting with 'Author:'.
The typical plugin will have at least the following functions:
- pluginname_load()
- pluginname_unload()
In our case, we'll call them randplace_load() and randplace_unload(), as that is the name of our plugin. These functions are called whenever we wish to either initialise the plugin or remove it from the current webpage. Also if your plugin requires things like altering the database schema before it can run for the very first time, you would likely place these instructions in the functions named
- pluginname_install()
- pluginname_uninstall()
Next we'll talk about hooks. Hooks are places in hubzilla code where we allow plugins to do stuff. There are a lot of these, and they each have a name. What we normally do is use the pluginname_load() function to register a "handler function" for any hooks you are interested in. Then when any of these hooks are triggered, your code will be called.
We register hook handlers with the 'register_hook()' function. It takes 3 arguments. The first is the hook we wish to catch, the second is the filename of the file to find our handler function (relative to the base of your hubzilla installation), and the third is the function name of your handler function. So let's create our randplace_load() function right now.
function randplace_load() {
register_hook('post_local', 'addon/randplace/randplace.php', 'randplace_post_hook');
register_hook('feature_settings', 'addon/randplace/randplace.php', 'randplace_settings');
register_hook('feature_settings_post', 'addon/randplace/randplace.php', 'randplace_settings_post');
}
So we're going to catch three events, 'post_local' which is triggered when a post is made on the local system, 'feature_settings' to set some preferences for our plugin, and 'feature_settings_post' to store those settings.
Next we'll create an unload function. This is easy, as it just unregisters our hooks. It takes exactly the same arguments.
function randplace_unload() {
unregister_hook('post_local', 'addon/randplace/randplace.php', 'randplace_post_hook');
unregister_hook('feature_settings', 'addon/randplace/randplace.php', 'randplace_settings');
unregister_hook('feature_settings_post', 'addon/randplace/randplace.php', 'randplace_settings_post');
}
Hooks are called with two arguments. The first is always $a, which is our global App structure and contains a huge amount of information about the state of the web request we are processing; as well as who the viewer is, and what our login state is, and the current contents of the web page we're probably constructing.
The second argument is specific to the hook you're calling. It contains information relevant to that particular place in the program, and often allows you to look at, and even change it. In order to change it, you need to add '&' to the variable name so it is passed to your function by reference. Otherwise it will create a copy and any changes you make will be lost when the hook process returns. Usually (but not always) the second argument is a named array of data structures. Please see the "hook reference" (not yet written as of this date) for details on any specific hook. Occasionally you may need to view the program source to see precisely how a given hook is called and how the results are processed.
Let's go ahead and add some code to implement our post_local hook handler.
function randplace_post_hook($a, &$item) {
/**
*
* An item was posted on the local system.
* We are going to look for specific items:
* - A status post by a profile owner
* - The profile owner must have allowed our plugin
*
*/
logger('randplace invoked');
if(! local_channel()) /* non-zero if this is a logged in user of this system */
return;
if(local_channel() != $item['uid']) /* Does this person own the post? */
return;
if(($item['parent']) || (! is_item_normal($item))) {
/* If the item has a parent, or isn't "normal", this is a comment or something else, not a status post. */
return;
}
/* Retrieve our personal config setting */
$active = get_pconfig(local_channel(), 'randplace', 'enable');
if(! $active)
return;
/**
*
* OK, we're allowed to do our stuff.
* Here's what we are going to do:
* load the list of timezone names, and use that to generate a list of world cities.
* Then we'll pick one of those at random and put it in the "location" field for the post.
*
*/
$cities = array();
$zones = timezone_identifiers_list();
foreach($zones as $zone) {
if((strpos($zone,'/')) && (! stristr($zone,'US/')) && (! stristr($zone,'Etc/')))
$cities[] = str_replace('_', ' ',substr($zone,strpos($zone,'/') + 1));
}
if(! count($cities))
return;
$city = array_rand($cities,1);
$item['location'] = $cities[$city];
return;
}
Now let's add our functions to create and store preference settings.
/**
*
* Callback from the settings post function.
* $post contains the global $_POST array.
* We will make sure we've got a valid user account
* and that only our own submit button was clicked
* and if so set our configuration setting for this person.
*
*/
function randplace_settings_post($a,$post) {
if(! local_channel())
return;
if($_POST['randplace-submit'])
set_pconfig(local_channel(),'randplace','enable',intval($_POST['randplace']));
}
/**
*
* Called from the Feature Setting form.
* The second argument is a string in this case, the HTML content region of the page.
* Add our own settings info to the string.
*
* For uniformity of settings pages, we use the following convention
* <div class="settings-block">
* <h3>title</h3>
* .... settings html - many elements will be floated...
* <div class="clear"></div> <!-- generic class which clears all floats -->
* <input type="submit" name="pluginnname-submit" class="settings-submit" ..... />
* </div>
*/
function randplace_settings(&$a,&$s) {
if(! local_channel())
return;
/* Add our stylesheet to the page so we can make our settings look nice */
head_add_css('/addon/randplace/randplace.css');
/* Get the current state of our config variable */
$enabled = get_pconfig(local_channel(),'randplace','enable');
$checked = (($enabled) ? ' checked="checked" ' : '');
/* Add some HTML to the existing form */
$s .= '<div class="settings-block">';
$s .= '<h3>' . t('Randplace Settings') . '</h3>';
$s .= '<div id="randplace-enable-wrapper">';
$s .= '<label id="randplace-enable-label" for="randplace-checkbox">' . t('Enable Randplace Plugin') . '</label>';
$s .= '<input id="randplace-checkbox" type="checkbox" name="randplace" value="1" ' . $checked . '/>';
$s .= '</div><div class="clear"></div>';
/* provide a submit button */
$s .= '<div class="settings-submit-wrapper" ><input type="submit" name="randplace-submit" class="settings-submit" value="' . t('Submit') . '" /></div></div>';
}
Advanced Plugins
Sometimes your plugins want to provide a range of new functionality which isn't provided at all or is clumsy to provide using hooks. In this case your plugin can also act as a 'module'. A module in our case refers to a structured webpage handler which responds to a given URL. Then anything which accesses that URL will be handled completely by your plugin.
The key to this is to create a simple function named pluginname_module() which does nothing.
function randplace_module() { return; }
Once this function exists, the URL https://yoursite/randplace
will access your plugin as a module. Then you can define functions which are called at various points to build a webpage just like the modules in the mod/ directory. The typical functions and the order which they are called is
modulename_init($a) // (e.g. randplace_init($a);) called first - if you wish to emit json or xml,
// you should do it here, followed by killme() which will avoid the default action of building a webpage
modulename_aside($a) // Often used to create sidebar content
modulename_post($a) // Called whenever the page is accessed via the "post" method
modulename_content($a) // called to generate the central page content. This function should return a string
// consisting of the central page content.
Your module functions have access to the URL path as if they were standalone programs in the Unix operating system. For instance if you visit the page
https://yoursite/randplace/something/somewhere/whatever
we will create an argc/argv list for use by your module functions
$x = argc(); $x will be 4, the number of path arguments after the sitename
for($x = 0; $x < argc(); $x ++)
echo $x . ' ' . argv($x);
0 randplace
1 something
2 somewhere
3 whatever
Porting Friendica Plugins
Hubzilla uses a similar plugin architecture to the Friendica project. The authentication, identity, and permissions systems are completely different. Many Friendica plugins can be ported reasonably easily by renaming a few functions - and then ensuring that the permissions model is adhered to. The functions which need to be renamed are:
Friendica's pluginname_install() is pluginname_load()
Friendica's pluginname_uninstall() is pluginname_unload()
Hubzilla has _install and _uninstall functions but these are used differently.
Friendica's "plugin_settings" hook is called "feature_settings"
Friendica's "plugin_settings_post" hook is called "feature_settings_post"
Changing these will often allow your plugin to function, but please double check all your permission and identity code because the concepts behind it are completely different in hubzilla. Many structured data names (especially DB schema columns) are also quite different.
Testing
Hubzilla uses [PHPUnit] for automated testing, often called unit testing or integration testing.
The tests are written as PHP classes, and live under the tests/unit
subdirectory of the main Hubzilla repository. This guide will explain how to run the tests, how the tests are structured, and how you can write your own tests.
Running the test suite
Installing the dependencies
To be able to run the tests you have to install the developer dependencies listen in the composer.json
file. Make sure you have [composer installed] on your development system, and then run:
% composer install
This will install phpunit and a few other dependencies. It will also update some files in the vendor/composer
subdirectory. This may seem a bit noisy, but it's important that these changes are not committed to the repository!
Warning: Do not commit the changes to the files in the vendor/composer
directory to your repository!
Setting up the test database
We have included a script (tests/create_test_db.sh
) that will help you set up the test database. You can run it like this:
% HZ_TEST_DB_TYPE=mysql ./tests/create_test_db.sh
If you are running PostgreSQL instead, you create the test db like this:
% HZ_TEST_DB_TYPE=postgres ./tests/create_test_db.sh
The script make some assumptions about your setup, but everything is configurable using environment variables. If you need any customization, see the script source for the details.
Running the tests
Once you have installed the developer dependencies and set up the test database, you can run the tests like this:
% ./vendor/bin/phpunit -c tests/phpunit.xml
Again, by default the configuration in tests/phpunit.xml
makes some assumptions about your setup, which can be overridden using environment variables. See tests/phpunit.xml
for the details.
You can also run a specific test, or a specific set of tests using the --filter
argument to PHPUnit:
% ./vendor/bin/phpunit -c tests/phpunit.xml --filter Autoname
Will run any test mathcing "Autoname".
Generating coverage reports
To generate coverage reports you need a driver that is able to generate the coverage info that PHPUnit will collect. We recommend [Xdebug], but see the PHPUnit page on code coverage analysis for alternatives and details.
With Xdebug properly set up, you can generate the coverage report like this:
% XDEBUG_MODE=coverage ./vendor/bin/phpunit -c ./tests/phpunit.xml
This will generate a number of HTML files in directories under the tests/results/coverage/
subdirectory.
Open the `index.php file in your web browser to view the stats.
Debugging
With Xdebug installed, you can also do step debugging and a number of other things to debug and get information about the execution of the tests. See the [Xdebug] pages and your prefered editor for how to set this up.
Test structure and organization
Tests are located in the tests/unit
subdirectory, and subdirectories under there again, more or less reflecting the directory layout of the core code repository.
Tests are written as PHP classes that extends the UnitTestCase class (located in tests/unit/UnitTestcase.php
). The file name and the test class it contains should be the same, and they must end with Test.php
.
Examples are:
tests/unit/includes/AccountTest.php
tests/unit/Lib/ActivityTest.php
The test classes contain one or more test functions, these must be public and prefixed with test
.
Here's an example:
use Zotlabs\Tests\Unit\UnitTestCase;
class AccountTest extends UnitTestCase {
public function test_get_account_by_id_returns_existing_account() {
$account = get_account_by_id(42);
$this->assertNotFalse($account);
$this->assertEquals(
$this->fixtures['account'][0]['account_email'],
$account['account_email']
);
}
Notice that we try to make the name of the test funtion as descriptive as possible.
The class can also contain any number of other functions to keep things tidy and nice. These can be private, protected or public as needed by the test code.
Results and artifacts from the test runs will be left in the tests/results/
directory. This will typically include the test log, code coverage report etc.
Hubzilla specific features
Test database access:
Ideally it should be able to test each part of the code in isolation, where any dependencies should be replaced by stubs, mocks or fakes.
This is not feasible with a legacy codebase like Hubzilla, that has not been written with testability in mind. For this reason we use a separate test database, and make it available to the code under test using the same mechanisms as Hubzilla normally do.
This means that any code that executes database queries will behave as normal when called by a test.
To make this work we connect to the database before running each test function. We also load some initial data into the database to make sure things work as expected, and that we have a known state of the database when the test runs.
When the test finishes, the test database is wiped clean, so that we have the same consistent state when the next test runs.
All of this is handled by the UnitTestCase
base class.
Database fixtures:
We need some predictable content in the database to set up things like logging, and other content that's useful in general for the tests. These are database fixtures that are loaded into the database for each test run.
The database fixtures are located in the tests/unit/include/dba/_files
directory, and consist of YAML files, where each file represents the content of a database table with the same name as the file itself.
While database fixtures are nice to have, we try to keep the number of them as minimal as possible. It's usually better to add any content needed for a specific test in the test itself.
Fakes:
Fakes are classes that we can pass to the code under test that will look and (to the code under test) behave the same as the original class. These are useful when we're able to pass objects to the code under test, as we can control it completely from the test code.
Fakes are located under the tests/fakes
directory.
Creating protocol federation services
There are three main components to writing federation plugins. These are:
- Channel discovery and making connections
- Sending posts/messages
- Receiving posts/messages
In addition, federation drivers must handle
- differences in privacy policies (and content formats)
Making connections
The core application provides channel discovery in the following sequence:
- Nomad channel discovery
- Webfinger (channel@hub) lookup 2.1 RFC7033 webfinger 2.2 XRD based webfinger (old style)
- URL discovery (currently only used to discover RSS feeds)
- If all of these fail, the network is "unknown" and we are unable to communicate with or connect with the channel. An 'xchan' record may still be created if there is enough information known to identify a unique channel.
Any of the lookup services may be bound to a plugin and extended. When a channel is discovered, we create an 'xchan' record which is a platform neutral representation of the channel identity. If we need to send information to the channel, a 'hubloc' (hub location) record is also generally required. A 'follow' plugin hook is provided to bypass webfinger and this discovery sequence completely.
The final step in gluing this together is to create an 'abook' record, which attaches an xchan in a relationship to a local channel with a given set of permissions.
For networks which do not support nomadic identity, your plugin must also set "abook_instance" which is a comma-separated list of local URLs that the remote channel is connected with. For instance if your member was connected to my channel clone at https://example.com, the abook_instance for that connection would read 'https://example.com'. If you also connected to my clone at https://abc.example.com, the string would be changed to 'https://example.com,https://abc.example.com'. This allows local channels to differentiate which instance a given remote channel is connected with and avoid delivery failures to those channels from other clone instances.
A federation plugin for a webfinger based system needs only to examine the webfinger or XRD results and identify a protocol stack which can be used to connect or communicate. Then create an xchan record with the given 'xchan_network' type and a hubloc record with the same 'hubloc_network' with the information given. Currently the plugin will need to create the entire record, but in the future this will be extended so that the plugin only need identify a network name and the record will be populated with all other known values.
An xchan record is always required in order to connect. To connect, create an abook record with the desired permissions.
Additional information that your plugin requires for communication can be stored in the xconfig table and/or abconfig table if there is no convenient or appropriate table column in the xchan or hubloc tables.
When a connection is made, we generally call the notifier (include/notifier.php) to send a message to the remote channel. This is bound to the hook 'permissions_create'. Your plugin will need to handle this in order to send a "follow" or "make friends" message to the other network.
Notes: The first stage Nomad lookup will be replaced with a webfinger lookup. This work is in progress. A separate lookup was required initially as webfinger does not allow non-SSL connections. We will provide non-SSL Nomad lookups (usually test and development sites) via the "old" XRD based webfinger to avoid this limitation.
The core application will attempt to create xchan records for projects identified as members of the "open web"; currently Hubzilla, Friendica, Diaspora, GNU-Social and Pump.io. This is so that comments can be passed amongst project sites and the network correctly identified. A federation plugin is required to fully federate with other networks, but comments may be passed to sites without such a plugin installed so that there are no unexplained holes in conversations.
The core application must also provide signing ability for Diaspora comments since they require a special signing format and must be signed by the comment author regardless of whether that channel federates with Diaspora. The owner of the conversation may federate with Diaspora so the comments must be signed. This is unfortunate but necessary.
Sending Messages
Whenever any message is sent (with the sole exception of directory communications), we invoke the notifier (include/notifier.php), and pass it the type of message and generally an identifier to lookup the information being sent from the database (items or conversational things, private mail, permissions updates, etc.).
The notifier has several hooks which may be used by plugins in different ways, depending on how their delivery loop works. For different message types and complex delivery situations you may need to tie into multiple hooks. The 'permissions_create' hook was mentioned in the first section. There is also a 'permissions_update' message if permissions have changed and the other end of the link needs to be advised. Few services will provide or handle this (as their permissions are static), but it is also used for instance to send profile and profile photo update messages and you may wish to handle this.
The next plugin hook is 'notifier_process'. It is passed an array providing the complete state of the notifier and is called once per notifier invocation. It contains the complete list of recipients (with xchan_network set for each).
There is also 'notifier_hub' which like 'notifier_process' is passed the complete state of the notifier, but the difference is that it is called for each individual hub or distinct URL delivery and may be matched on the hubloc_network type. Hub delivery is much more efficient than recipient delivery but may not be suitable for all protocol stacks.
Your plugin will be required to understand the message state and recipients and translate the sent item to the desired format. You will also be required to check privacy and block communication to anybody but the intended recipients - if it is a private communication. The plugin will often at this point stick the message into the queue and return the queue id to the notifier.
Queue handlers exist already for simple posted data. If you create a queue entry with 'post' type we'll open an HTTP POST request and post the data provided and acknowledge success or failure. You can create other forms of communication by providing a different outq_driver type and handling the processing of queue requests yourself. Delivery reporting is available if you wish to acknowledge different error conditions, or anything beyond success/failure. Advanced delivery reporting will also require a custom queue type. The basic 'post' type only deals with success (communication successful with the remote site) and failure.
Receiving Messages
Receiving messages from the remote network will probably require a 'receive' endpoint or module dedicated to your network communication protocol. This is a URL route that your plugin may need to register with the'module_loaded' hook. You module will then take responsibility for importing any data which arrives at that endpoint and translating it to the format required for this project and storing the resulting data. The basic structure we use is an extensible activitystream item but with slightly different field names and several optional fields. It can be easily mapped to an activitystream. Additional data can be stored in the "iconfig" table. item_store() and item_store_update() are generally used to store the data and send appropriate notifications. Similiar facilities are available for private mail and profile information.
Code of conduct for contributors
Our pledge
In the interest of fostering an open and welcoming environment, we as contributors and carers pledge to make participation in our project and community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, experience level, nationality, personal appearance, ethnicity, religion or sexual identity and orientation.
Our standards
Examples of behaviours that contribute to creating a positive environment include
- Using welcoming and inclusive language
- Respecting different points of view and experiences
- Accepting constructive criticism gracefully
- Focusing on what is best for the community
- Showing empathy towards other members of the community
Examples of unacceptable behaviour from participants include
- The use of sexualised language or images and unwanted sexual attention or advances
- Trolling, offensive/degrading comments and personal or political attacks
- Public or private harassment
- Publishing the private information of others, such as a physical or electronic address, without express permission
- Other behaviour that could be considered inappropriate in a professional environment
Our responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behaviour and are expected to take appropriate and fair corrective action when unacceptable behaviour is identified. Project maintainers have the right and responsibility to remove, edit or reject comments, commits, code, wiki edits, issues and other contributions that do not comply with this Code of Conduct, or to temporarily or permanently ban contributions that they deem inappropriate, threatening, offensive or harmful.
Scope
This Code of Conduct applies both within the project areas and in the public sphere when a person is representing the project or its community. Examples of representing a project or community include using an official project email address, posting via an official social media account or acting as an appointed representative at an online or offline event. The representation of a project can be further defined and specified by the project supervisors.
Enforcement
Instances of offensive, harassing or otherwise unacceptable behaviour can be reported to the project team at project@hubzilla.org. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is committed to confidentiality towards the person reporting an incident. Further details of specific enforcement policies may be published separately. Project supervisors who fail to follow or enforce the Code of Conduct in good faith may face temporary or permanent consequences as determined by other members of the project management team.
Attribution
This Code of Conduct is an adaptation of the Contributor Covenant, version 1.4, available at http://contributor-covenant.org/version/1/4.
The Nomad protocol
What is Nomad?
Nomad is the revolutionary protocol that powers Hubzilla, enabling communication, identity management and access control across a fully decentralised network of independent websites, often referred to as ‘the grid’. The resulting platform is a robust system that supports privacy and security while enabling the kind of rich web services typically found only in centralised, proprietary solutions.
Consider this typical scenario:
Jaquelina wants to share photos from her blog at jaquelina.org with Roberto, but with no one else. Roberto maintains his own family hub at roberto.net on a completely independent server. Nomad allows Jaquelina to publish her photos with an access control list (ACL) that only includes Roberto. This means that while Roberto can see the photos when he visits her blog, his brother Marco cannot, nor can any other family member who has an account on roberto.net.
The twist in this scenario is the fact that Roberto never logged in to Jaquelina's website. Instead, he only had to log in once with his password on his own roberto.net website. When Roberto visits jaquelina.org, he is seamlessly authenticated by their hub by querying his server in the background.
It's not uncommon for servers to have technical issues or become inaccessible for various reasons. Nomad provides robustness to Roberto's online activities by allowing him to have clones of his online identity or channel on multiple independent hubs. Imagine that Roberto's server goes down for some reason and he can no longer log in there. He simply logs into one of his clones on gadfly.com, a website run by his friend Peter. Once he has authenticated himself at gadfly.com, Roberto can view Jaquelina's blog as before, without Jaquelina having to grant additional access!
Communication
Communication and social networks are an essential part of the grid. Any channel (and any service provided by that channel) can take full advantage of feature-rich social communication on a global scale. These communications can be public or private - and private communications include not only fully encrypted transport, but also encrypted storage to protect against accidental snooping and disclosure by rogue system administrators and ISPs.
Nomad supports a wide range of background services in the grid, from friend suggestions to directory services. New content and data updates are passed in the background between hubs across the grid according to the access control lists and permissions set by the sender and receiver channels. Data is also synchronised between any number of channel clones so that hub members can access data and continue to collaborate seamlessly even if their primary hub is unavailable or offline.
Identity
Nomad's identity layer is unique. It provides an invisible single sign-on for all locations in the grid. It also provides a nomadic identity so that your communication with friends, family or other people you communicate with is not affected by the loss of your primary communication hub - either temporarily or permanently.
The important parts of your identity and relationships can be backed up on a USB stick or your laptop and appear on any node on the network at any time - with all your friends and preferences. Crucially, these nomadic instances are kept synchronised so that any instance can take over if another is compromised or corrupted. This not only protects you from major system failures, but also from temporary website overload and government tampering or censorship.
We believe that Hubzilla's nomadic identity, single sign-on and decentralisation bring a high level of resilience and consistency to internet communications, which is much needed in the face of global trends towards corporate centralisation and mass and indiscriminate government surveillance and censorship. When browsing the web, viewing channels and their unique content, you are seamlessly authenticated, even across completely different server hubs. No need to enter passwords. You don't have to type anything. You are simply greeted with your name on every new page you visit.
How does this work with Nomad? We call it ‘magic-auth’ because Hubzilla hides the details of the complexity of single sign-on logins and nomadic identities from web browsing. This is one of Hubzilla's design goals: to increase privacy and freedom on the Internet while reducing the complexity and tedium of having to enter new passwords and login names every time you visit the Internet. You only log in once to your home hub (or a nomadic backup hub of your choice). This allows you to access all authenticated services offered anywhere on the web - such as shopping, blogs, forums and access to private information. Your password is not stored on a thousand different websites, but on servers that you control or trust.
They cannot be silenced. They cannot be removed from the network unless you choose to leave it yourself.
Access control
Nomad's identity layer allows you to assign fine-grained permissions to any content you want to publish - and these permissions extend across the entire grid. It's like having a huge website made up of an army of small individual websites - where each channel in the grid can fully control its privacy and sharing preferences for all the web resources it creates.
Currently, Hubzilla supports access control for many types of data, including discussion posts and comments, photo albums, events, cloud files, web pages, wikis and more. Each item and how and with whom it is shared is completely under your control.
This type of control is trivial with large enterprise providers as they own the user database. In the Grid, you don't need a huge user database on your computer - because the Grid is your user database. It has essentially infinite capacity (limited by the total number of hubs online on the Internet) and is distributed across hundreds, possibly even millions of computers.
Access can be granted or denied to any resource, channel or group of channels - anywhere on the grid. Others can access your content if you allow them to, and they don't even need to have an account in your hub.
Magic Auth
The so-called ‘magic auth’ takes place via a special exchange. On the remote computer, a redirection is made to the Nomad endpoint with special GET parameters.
Endpoint: https://example.com/post/name
where ‘name’ is the left-hand side of the channel webbie, for example ‘mike’ if the webbie is ‘mike@zothub.com’.
In addition, four parameters are passed:
- auth => the webbie of the person requesting access
- dest => the desired destination URL (urlencoded)
- sec => a random string, which is also stored locally for use during the verification phase.
- version => the Zot revision
When this packet is received, a Nomad message is sent to the auth identity:
{
‘type’: ‘auth_check’,
‘sender’:{
‘guid’: ‘kgVFf_1_SSbyqH-BNWjWuhAvJ2EhQBTUdw-Q1LwwssAntr8KTBgBSzNVzUm9_RwuDpxI6X8me_QQhZMf7RfjdA’,
‘guid_sig’: "PT9-TApzpm7QtMxC63MjtdK2nUyxNI0tUoWlOYTFGke3kNdtxSzSvDV4uzq_7SSBtlrNnVMAFx2_1FDgyKawmqVtRPmT7QSXrKOL2oPzL8Hu_nnVVTs_0YOLQJJ0GYACOOK- R5874WuXLEept5-KYg0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81- Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91- ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD- yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q’,
‘url’: ‘http:\/\/podunk.edu’,
‘url_sig’: "T8Bp7j5DHHhQDCFcAHXfuhUfGk2P3inPbImwaXXF1xJd3TGgluoXyyKDx6WDm07x0hqbupoAoZB1qBP3_WfvWiJVAK4N1FD77EOYttUEHZ7L43xy5PCpojJQmkppGbPJc2jnTIc_F1vvGvw5fv8gBWZvPqTdb6LWF6FLrzwesZpi7j2rsioZ3wyUkqb5TDZaNNeWQrIEYXrEnWkRI_qTSOzx0dRTsGO6SpU1fPWuOOYMZG8Nh18nay0kLpxReuHCiCdxjXRVvk5k9rkcMbDBJcBovhiSioPKv_yJxcZVBATw3z3TTE95kGi4wxCEenxwhSpvouwa5b0hT7NS4Ay70QaxoKiLb3ZjhZaUUn4igCyZM0h6fllR5I6J_sAQxiMYD0v5ouIlb0u8YVMni93j3zlqMWdDUZ4WgTI7NNbo8ug9NQDHd92TPmSE1TytPTgya3tsFMzwyq0LZ0b- g- zSXWIES__jKQ7vAtIs9EwlPxqJXEDDniZ2AJ6biXRYgE2Kd6W_nmI7w31igwQTms3ecXe5ENI3ckEPUAq__llNnND7mxp5ZrdXzd5HHU9slXwDShYcW3yDeQLEwAVomTGSFpBrCX8W77n9hF3JClkWaeS4QcZ3xUtsSS81yLrp__ifFfQqx9_Be89WVyIOoF4oydr08EkZ8zwlAsbZLG7eLXY"
},
‘recipients’:{
{
‘guid’: ‘ZHSqb3yGar3TYV_o9S-JkD-6o_V4DhUcxtv0VeyX8Kj_ENHPI_M3SyAUucU835-mIayGMmTpqJz3ujPkcavVhA’,
‘guid_sig’: "JsAAXigNghTkkbq8beGMJjj9LBKZn28hZ-pHSsoQuwYWvBJ2lSnfc4r9l--WgO6sucH-SR6RiBo78eWn1cZrh_cRMu3x3LLx4y-tjixg- oOOgtZakkBg4vmOhkKPkci0mFtzvUrpY4YHySqsWTuPwRx_vOlIYIGEY5bRXpnkNCoC8J4EJnRucDrgSARJvA8QQeQQL0H4mWEdGL7wlsZp_2VTC6nEMQ48Piu6Czu5ThvLggGPDbr7PEMUD2cZ0jE4SeaC040REYASq8IdXIEDMm6btSlGPuskNh3cD0AGzH2dMciFtWSjmMVuxBU59U1I6gHwcxYEV6BubWt_jQSfmA3EBiPhKLyu02cBMMiOvYIdJ3xmpGoMY1Cn__vhHnx_vEofFOIErb6nRzbD- pY49C28AOdBA5ffzLW3ss99d0A-6GxZmjsyYhgJu4tFUAa7JUl84tMbq28Tib0HW6qYo6QWw8K1HffxcTpHtwSL5Ifx0PAoGMJsGDZDD1y_r9a4vH5pjqmGrjL3RXJJUy- m4eLV5r7xMWXsxjqu3D8r04_dcw4hwwexpMT1Nwf8CTB0TKb8ElgeOpDFjYVgrqMYWP0XdhatcFtAJI7gsS-JtzsIwON9Kij66-VAkqy_z1IXI0ziyqV1yapSVYoUV1vMScRZ_nMqwiB5rEDx-XLfzko"
}
}
‘callback’:‘\/post’,
‘version’:1,
‘secret’:‘1eaa6613699be6ebb2adcefa5379c61a3678aa0df89025470fac871431b70467’,
‘secret_sig’: "eKV968b1sDkOVdSMi0tLRtOhQw4otA8yFKaVg6cA4I46_zlAQGbFptS-ODiZlSAqR7RiiZQv4E2uXCKar52dHo0vvNKbpOp_ezWYcwKRu1shvAlYyytsflH5acnDWL-FKOOgz5zqLLZ6cKXFMoR1VJGG_Od- DKjSwajyV9uVzTry58Hz_w0W2pjxwQ-Xv11rab5R2O4kKSW77YzPG2R5E6Q7HN38FrLtyWD_ai3K9wlsFOpwdYC064dk66X7BZtcIbKtM6zKwMywcfJzvS5_0U5yc5GGbIY_lY6SViSfx9shOKyxkEKHfS29Ynk9ATYYGnwO-jnlMqkJC7t149H- sI9hYWMkLuCzaeLP56k2B2B2TmtnYvE_vHNQjzVhTwuHCIRVr-p6nplQn_P3SkOpYqPi3k_tnnOLa2d3Wtga8ClEY90oLWFJC3j2UkBf_VEOBNcg-t5XO3T-j9O4Sbk96k1Qoalc-QlznbGx4bOVsGkRBBMiH4YUqiiWB_OkFHtdqv7dqGeC- u-B4u9IxzYst46vvmyA3O-Q4APSZ1RY8ITUH0jLTbh6EAV7Oki8pIbOg0t56p-8RlanOZqmFvR-grVSc7Ak1ZcD8NACmvidUpa1B7WEvRcOeffx9lype0bt5XenDnMyx6szevwxZIiM8qGM2lsSk4fu8HI9cW0mLywzZT0"
}
auth_check messages MUST be encrypted. This message is sent to the originating site, which checks whether the ‘secret’ matches the ‘sec’ it originally transmitted. It also checks secret_sig, which is signed with the private key of the destination channel and encoded with base64url. If everything is OK, a json packet is returned:
{
‘success’:1,
‘confirm’: "q0Ysovd1uQRsur2xG9Tg6bC23ynzw0191SkVd7CJcYoaePy6e_v0vnmPg2xBUtIaHpx_aSuhgAkd3aVjPeaVBmts6aakT6a_yAEy7l2rBydntu2tvrHhoVqRNOmw0Q1tI6hwobk1BgK9Pm0lwOeAo8Q98BqIJxf47yO9pATa0wktOg6a7LMogC2zkkhwOV5oEqjJfeHeo27TiHr1e2WaphfCusjmk27V_FAYTzw05HvW4SPCx55EeeTJYIwDfQwjLfP4aKV- I8HQCINt-2yxJvzH7Izy9AW- 7rYU0Il_gW5hrhIS5MTM12GBXLVs2Ij1CCLXIs4cO0x6e8KEIKwIjf7iAu60JPmnb_fx4QgBlF2HLw9vXMwZokor8yktESoGl1nvf5VV5GHWSIKAur3KPS2Tb0ekNh-tIk9u-xob4d9eIf6tge_d3aq1LcAtrDBDLk8AD0bho5zrVuTmZ9k- lBVPr_DRHSV_dlpu088j3ThaBsuV1olHK3vLFRhYCDIO0CqqK5IuhqtRNnRaqhlNN6fQUHpXk2SwHiJ2W36RCYMTnno6ezFk_tN-RA2ly- FomNZoC5FPA9gFwoJR7ZmVFDmUeK3bW-zYTA5vu15lpBPnt7Up_5rZKkr0WQVbhWJmylqOuwuNWbn3SrMQ8rYFZ23Tv300cOfKVgRBaePWQb4"
}
‘confirm’ in this case is the base64url-encoded RSA signature of the concatenation of “secret” with the base64url-encoded whirlpool hash of the source guid and guid_sig; signed with the private key of the source channel. This prevents a manin-the-middle from inserting a fraudulent success packet. Upon receipt and successful verification of this packet, the destination page is redirected to the original destination URL and displays a successful remote login.
Nomad structures
Nomad signatures
All signed data in Nomad is generated by an RSA signature operation with the initiator's private key. The binary result is then encoded for transport with base64url.
Nomad encryption
Encryption is currently performed using AES256CTR. Other algorithms MAY be supported. A 32-octet key and a 16-octet initialisation vector are generated at random. The desired data is then encoded with these generated strings and the result is base64url encoded. An array is then created:
- data The base64url-encoded encrypted data
- alg The selected algorithm, in this case the character string ‘aes256ctr’.
- key The randomly generated key, RSA-encrypted with the recipient's public key, and the base64url-encoded result
- iv The randomly generated initialisation vector, RSA-encrypted with the recipient's public key, and the base64url-encoded result
Basic Nomad packet
Used to initiate a dialogue with another Nomad site. This packet MAY be encrypted. The presence of an array element ‘iv’ indicates that encryption has been performed. When sending an ‘auth_check’ packet, this packet MUST be encrypted, using the target site's public key (the site key, as opposed to a sender key).
{
‘type’: ‘notify’,
‘sender’:{
‘guid’: ‘kgVFf_1_SSbyqH-BNWjWuhAvJ2EhQBTUdw-Q1LwwssAntr8KTBgBSzNVzUm9_RwuDpxI6X8me_QQhZMf7RfjdA’,
‘guid_sig’: "PT9-TApzpm7QtMxC63MjtdK2nUyxNI0tUoWlOYTFGke3kNdtxSzSvDV4uzq_7SSBtlrNnVMAFx2_1FDgyKawmqVtRPmT7QSXrKOL2oPzL8Hu_nnVVTs_0YOLQJJ0GYACOOK- R5874WuXLEept5-KYg0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81- Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91- ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD- yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q’,
‘url’: ‘http:\/\/podunk.edu’,
‘url_sig’: "T8Bp7j5DHHhQDCFcAHXfuhUfGk2P3inPbImwaXXF1xJd3TGgluoXyyKDx6WDm07x0hqbupoAoZB1qBP3_WfvWiJVAK4N1FD77EOYttUEHZ7L43xy5PCpojJQmkppGbPJc2jnTIc_F1vvGvw5fv8gBWZvPqTdb6LWF6FLrzwesZpi7j2rsioZ3wyUkqb5TDZaNNeWQrIEYXrEnWkRI_qTSOzx0dRTsGO6SpU1fPWuOOYMZG8Nh18nay0kLpxReuHCiCdxjXRVvk5k9rkcMbDBJcBovhiSioPKv_yJxcZVBATw3z3TTE95kGi4wxCEenxwhSpvouwa5b0hT7NS4Ay70QaxoKiLb3ZjhZaUUn4igCyZM0h6fllR5I6J_sAQxiMYD0v5ouIlb0u8YVMni93j3zlqMWdDUZ4WgTI7NNbo8ug9NQDHd92TPmSE1TytPTgya3tsFMzwyq0LZ0b- g- zSXWIES__jKQ7vAtIs9EwlPxqJXEDDniZ2AJ6biXRYgE2Kd6W_nmI7w31igwQTms3ecXe5ENI3ckEPUAq__llNnND7mxp5ZrdXzd5HHU9slXwDShYcW3yDeQLEwAVomTGSFpBrCX8W77n9hF3JClkWaeS4QcZ3xUtsSS81yLrp__ifFfQqx9_Be89WVyIOoF4oydr08EkZ8zwlAsbZLG7eLXY"
‘sitekey’:"-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxTeIwXZWrw/S+Ju6gewh
LgkKnNNe2uCUqCqMZoYgJar3T5sHCDhvXc4dDCbDkxVIaA/+V1mURtBV60a3IGjn
OAO0W0XGGLe2ED7G5o9U8T9mVGq8Mauv0v1oQ5wIR1gEAhBavkQ2OUGuF/YKn2nj
HlKsv9HzUAHpcDMUe3Uklc2RhQbMcnJxEgkyjCkDyrTtCZzISkTAocHvpCG1KSog
njUZdiz9UWxvM4rCFkCJvQU4RwRZJb7GA9ul+9JrF7NvUQTx8csRP2weBk1E9yyj
wbe187E0eVj9RXX2Mx3mYhgrTdodxLOVMSXZLg1/SMpeFFl7QBhuM0SiOPg8a7Et
e2iNA/RD4WiUFqCDfafasRa1TOozOm7LA+08lkAh5PeQPJsJbrX0wVVft++Y+5/z
BvcUOP73vcbz7j5hJ7HLsbQtye/UUCfODBFybuDqRM84Aet8rjZohX7vukXdMD4I
2HuB7pjR4uIfyYr0J63ANkvbsn8LR+RnirmHrK5H/OgxxjXcfYbGEQgFxvxhF6lA
FpMu6Do4dx3CIb6pRmZ8bjSImXexJ0BSo9n3gtrz0XYLecsYFlQ9+QQjm83qxyLb
M23in0xqMVsyQvzjNkpImrO/QdbEFRIIMee83IHq+adbyjQR49Z2hNEIZhkLPc3U
2cJJ2HkzkOoF2K37qwIzk68CAwEAAQ==
-----END PUBLIC KEY-----
"
},
‘recipients’:{
{
‘guid’: ‘lql-1VnxtiO4-WF0h72wLX1Fu8szzHDOXgQaTbELwXW77k8AKFfh-hYr70vqMrc3SSvWN-Flrc5HFhRTWB7ICw’,
‘guid_sig’: "PafvEL0VpKfxATxlCqDjfOeSIMdmpr3iU7X-Sysa1h5LzDpjSXsjO37tYZL- accb1M5itLlfnW5epkTa5I4flsW21zSY1A2jCuBQUTLLGV7rNyyBy7lgqJUFvAMRx0TfXzP9lcaPqlM9T1tA6jfWOsOmkdzwofGeXBnsjGfjsO2xdGYe6vwjOU0DSavukvzDMnOayB9DekpvDnaNBTxeGLM45Skzr7ZEMcNF7TeXMbnvpfLaALYEKeQs9bGH- UgAG8fBWgzVAzeBfx_XSR1rdixjyiZGP0kq0h35SlmMPcEjliodOBFwMXqpXFB7Ibp4F6o6te2p2ErViJccQVG8VNKB6SbKNXY6bhP5zVcVsJ- vR-p4xXoYJJvzTN7yTDsGAXHOLF4ZrXbo5yi5gFAlIrTLAF2EdWQwxSGyLRWKxG8PrDkzEzX6cJJ0VRcLh5z6OI5QqQNdeghPZbshMFMJSc_ApCPi9_hI4ZfctCIOi3T6bdgTNKryLm5fhy_eqjwLAZTGP- aUBgLZpb1mf2UojBn6Ey9cCyq-0T2RWyk-FcIcbV4qJ-p_8oODqw13Qs5FYkjLr1bGBq82SuolkYrXEwQClxnrfKa4KYc2_eHAXPL01iS9zVnI1ySOCNJshB97Odpooc4wk7Nb2Fo-Q6THU9zuu0uK_-JbK7IIl6go2qA"
},
},
‘callback’:‘\/post’,
‘version’: ‘1.2’,
‘encryption’:{
‘aes256ctr’
},
‘secret’:‘1eaa6613699be6ebb2adcefa5379c61a3678aa0df89025470fac871431b70467’,
‘secret_sig’: "0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81-Xvs8I2kj6y5vfFtm- FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91-ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs- pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD- yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q"
}
type
The message type: notify, purge, refresh, force_refresh, auth_check, ping or pickup. The content of the packets varies depending on the message type. The notify packet is described here.
callback
A character string that is appended to the URL and identifies the Nomad communication endpoint on this system. This is usually the character string ‘/post’.
version
The identifier of the Nomad protocol so that future protocol revisions can co-exist.
encryption
Array of supported encryption algorithms, ordered by decreasing preference. If no compatible encryption methods are specified, applications MUST use ‘aes256cbc’.
secret
A 64-character string randomly generated by the sending side.
secret_sig
The RSA signature of the secret, signed with the sender's private key.
sender
An array of four components that provide a portable identity. We can contact the given URL and download a Nomad info packet to obtain the sender's public key and use it to verify the sender's guid and the signatures of the sending URL.
- guid Usually a 64 character base64url encoded string. It is generated when an identity is created and an attempt is made to make it unique, but this is not required.
- guid_sig The RSA signature of the guid, signed with the sender's private key and base64url-encoded.
- url The base URL of the location from which this post originated.
- url_sig The RSA signature of the url, signed with the sender's private key and base64url encoded.
- sitekey The public key of the website specified in the url
recipients
Only used for private messages. An array of envelope recipients. Each recipient is represented by an array of guid and guid_sig. If recipients are specified, the entire packet is also encapsulated with a negotiated cryptographic algorithm or ‘aes256cbc’ if none could be negotiated.
- guid The guid of a private recipient.
- guid_sig The RSA signature of the guid, signed with the recipient's private key and base64url-encoded
Zot API
Many existing social applications and tools can interface directly using the Twitter/StatusNet API, which is available using the 'twitter_api' addon.
This document describes the native API; which allows direct programmatic access to several internal data structures and libraries extending beyond the basic social interface.
The API endpoints detailed below are relative to api/z/1.0
, meaning that if an API is listed as channel/stream
the full API URL is https://hub.hubzilla.hu/api/z/1.0/channel/stream
.
channel/export/basic
Export basic channel data
Options: - sections comma-separated list of data types to export
- posts if true, return default sections plus 3 months of posts
If no sections are requested, the following sections are returned: channel, connections, config, apps, chatrooms, events, webpages, mail, wikis
Files and large collections of posts may run into memory limits; these must generally be requested separately.
channel/stream
Fetch channel conversation items
network/stream
Fetch network conversation items
files
List file storage (attach DB)
GET /api/z/1.0/files
Options:
- filehash return only entries matching hash (exactly)
- filename return only entries matching filename (substring)
- filetype return only entries matching filetype/mimetype (substring)
- start start at record (default 0)
- records number of records to return or 0 for unlimited
Example:
curl -u mychannel:mypassword https://xyz.macgirvin.com/api/z/1.0/files -d filetype=multipart/mixed
Returns:
{
"success": true,
"results": [
{
"id": "1",
"aid": "1",
"uid": "2",
"hash": "44ee8b2a1a7f36dea07b93b7747a2383a1bc0fdd08339e8928bfcbe45f65d939",
"filename": "Profile Photos",
"filetype": "multipart/mixed",
"filesize": "0",
"revision": "0",
"folder": "",
"os_storage": "1",
"is_dir": "1",
"is_photo": "0",
"flags": "0",
"created": "2016-01-02 21:51:17",
"edited": "2016-01-02 21:51:17",
"allow_cid": "",
"allow_gid": "",
"deny_cid": "",
"deny_gid": ""
},
{
"id": "12",
"aid": "1",
"uid": "2",
"hash": "71883f1fc64af33889229cbc79c5a056deeec5fc277d765f182f19073e1b2998",
"filename": "Cover Photos",
"filetype": "multipart/mixed",
"filesize": "0",
"revision": "0",
"folder": "",
"os_storage": "1",
"is_dir": "1",
"is_photo": "0",
"flags": "0",
"created": "2016-01-15 00:24:33",
"edited": "2016-01-15 00:24:33",
"allow_cid": "",
"allow_gid": "",
"deny_cid": "",
"deny_gid": ""
},
{
"id": "16",
"aid": "1",
"uid": "2",
"hash": "f48f7ec3278499d1dd86b72c3207beaaf4717b07df5cc9b373f14d7aad2e1bcd",
"filename": "2016-01",
"filetype": "multipart/mixed",
"filesize": "0",
"revision": "0",
"folder": "",
"os_storage": "1",
"is_dir": "1",
"is_photo": "0",
"flags": "0",
"created": "2016-01-22 03:24:55",
"edited": "2016-01-22 03:26:57",
"allow_cid": "",
"allow_gid": "",
"deny_cid": "",
"deny_gid": ""
}
]
}
filemeta
Export file metadata for any uploaded file
filedata
Provides the ability to download a file from cloud storage in chunks
GET /api/z/1.0/filedata
Required:
- file_id attach.hash of desired file ('begins with' match)
Optional:
- start starting byte of returned data in file (counting from 0)
- length length (prior to base64 encoding) of chunk to download
Returns:
attach (DB) structure with base64 encoded 'content' comprised of the desired chunk
Example:
https://xyz.macgirvin.com/api/z/1.0/filedata?f=&file_id=9f5217770fd&start=0&length=48
Returns:
{
"attach": {
"id": "107",
"aid": "1",
"uid": "2",
"hash": "9f5217770fd55d563bd77f84d534d8e119a187514bbd391714626cd9c0e60207",
"creator": "pgcJx1IQjuPkx8aI9qheJlBMZzJz-oTPjHy3h5pWlOVOriBO_cSiUhhqwhuZ74TYJ8_ECO3pPiRMWC0q8YPCQg",
"filename": "pcxtopbm.c",
"filetype": "application/octet-stream",
"filesize": "3934",
"revision": "0",
"folder": "",
"flags": "0",
"is_dir": "0",
"is_photo": "0",
"os_storage": "1",
"os_path": "",
"display_path": "",
"content": "LyogcGN4dG9wYm0uYyAtIGNvbnZlcnQgUEMgcGFpbnRicnVzaCAoLnBjeCkgZmls",
"created": "2016-07-24 23:13:01",
"edited": "2016-07-24 23:13:01",
"allow_cid": "",
"allow_gid": "",
"deny_cid": "",
"deny_gid": "",
"start": 0,
"length": 48
}
}
file/export
file
albums
Description: list photo albums
GET /api/z/1.0/albums
Output:
text - textual name
total - number of photos in this album
url - web URL
urlencode - textual name, urlencoded
bin2hex - textual name using bin2hex (which is used in the web URL link)
Example:
{
"success": true,
"albums": [
{
"text": "/",
"total": "2",
"url": "https://xyz.macgirvin.com/photos/hubzilla/album/",
"urlencode": "",
"bin2hex": ""
},
{
"text": "2016-01",
"total": "6",
"url": "https://xyz.macgirvin.com/photos/hubzilla/album/323031362d3031",
"urlencode": "2016-01",
"bin2hex": "323031362d3031"
},
{
"text": "2016-02",
"total": "7",
"url": "https://xyz.macgirvin.com/photos/hubzilla/album/323031362d3032",
"urlencode": "2016-02",
"bin2hex": "323031362d3032"
},
{
"text": "Cover Photos",
"total": "5",
"url": "https://xyz.macgirvin.com/photos/hubzilla/album/436f7665722050686f746f73",
"urlencode": "Cover+Photos",
"bin2hex": "436f7665722050686f746f73"
},
{
"text": "Profile Photos",
"total": "26",
"url": "https://xyz.macgirvin.com/photos/hubzilla/album/50726f66696c652050686f746f73",
"urlencode": "Profile+Photos",
"bin2hex": "50726f66696c652050686f746f73"
}
]
}
photos
list photo metadata
photo
group
GET /api/z/1.0/group
Description: list privacy groups
Returns: DB tables of all privacy groups.
To use with API group_members, provide either 'group_id' from the id element returned in this call, or 'group_name' from the gname returned in this call.
[
{
"id": "1",
"hash": "966c946394f3e2627bbb8a55026b5725e582407098415c02f85232de3f3fde76Friends",
"uid": "2",
"visible": "0",
"deleted": "0",
"gname": "Friends"
},
{
"id": "2",
"hash": "852ebc17f8c3ed4866f2162e384ded0f9b9d1048f93822c0c84196745f6eec66Family",
"uid": "2",
"visible": "1",
"deleted": "0",
"gname": "Family"
},
{
"id": "3",
"hash": "cc3cb5a7f9818effd7c7c80a58b09a189b62efa698a74319117babe33ee30ab9Co-workers",
"uid": "2",
"visible": "0",
"deleted": "0",
"gname": "Co-workers"
}
]
group_members
GET /api/z/1.0/group_members
Required:
group_id or group_name
Returns:
group_member+abook+xchan (DB join) for each member of the privacy group
[
{
"id": "1",
"uid": "2",
"gid": "1",
"xchan": "pgcJx1IQjuPkx8aI9qheJlBMZzJz-oTPjHy3h5pWlOVOriBO_cSiUhhqwhuZ74TYJ8_ECO3pPiRMWC0q8YPCQg",
"abook_id": "2",
"abook_account": "1",
"abook_channel": "2",
"abook_xchan": "pgcJx1IQjuPkx8aI9qheJlBMZzJz-oTPjHy3h5pWlOVOriBO_cSiUhhqwhuZ74TYJ8_ECO3pPiRMWC0q8YPCQg",
"abook_my_perms": "218555",
"abook_their_perms": "0",
"abook_closeness": "0",
"abook_created": "2016-01-02 21:16:26",
"abook_updated": "2016-01-02 21:16:26",
"abook_connected": "0000-00-00 00:00:00",
"abook_dob": "0000-00-00 00:00:00",
"abook_flags": "0",
"abook_blocked": "0",
"abook_ignored": "0",
"abook_hidden": "0",
"abook_archived": "0",
"abook_pending": "0",
"abook_unconnected": "0",
"abook_self": "1",
"abook_feed": "0",
"abook_profile": "",
"abook_incl": "",
"abook_excl": "",
"abook_instance": "",
"xchan_hash": "pgcJx1IQjuPkx8aI9qheJlBMZzJz-oTPjHy3h5pWlOVOriBO_cSiUhhqwhuZ74TYJ8_ECO3pPiRMWC0q8YPCQg",
"xchan_guid": "lql-1VnxtiO4-WF0h72wLX1Fu8szzHDOXgQaTbELwXW77k8AKFfh-hYr70vqMrc3SSvWN-Flrc5HFhRTWB7ICw",
"xchan_guid_sig": "PafvEL0VpKfxATxlCqDjfOeSIMdmpr3iU7X-Sysa1h5LzDpjSXsjO37tYZL-accb1M5itLlfnW5epkTa5I4flsW21zSY1A2jCuBQUTLLGV7rNyyBy7lgqJUFvAMRx0TfXzP9lcaPqlM9T1tA6jfWOsOmkdzwofGeXBnsjGfjsO2xdGYe6vwjOU0DSavukvzDMnOayB9DekpvDnaNBTxeGLM45Skzr7ZEMcNF7TeXMbnvpfLaALYEKeQs9bGH-UgAG8fBWgzVAzeBfx_XSR1rdixjyiZGP0kq0h35SlmMPcEjliodOBFwMXqpXFB7Ibp4F6o6te2p2ErViJccQVG8VNKB6SbKNXY6bhP5zVcVsJ-vR-p4xXoYJJvzTN7yTDsGAXHOLF4ZrXbo5yi5gFAlIrTLAF2EdWQwxSGyLRWKxG8PrDkzEzX6cJJ0VRcLh5z6OI5QqQNdeghPZbshMFMJSc_ApCPi9_hI4ZfctCIOi3T6bdgTNKryLm5fhy_eqjwLAZTGP-aUBgLZpb1mf2UojBn6Ey9cCyq-0T2RWyk-FcIcbV4qJ-p_8oODqw13Qs5FYkjLr1bGBq82SuolkYrXEwQClxnrfKa4KYc2_eHAXPL01iS9zVnI1ySOCNJshB97Odpooc4wk7Nb2Fo-Q6THU9zuu0uK_-JbK7IIl6go2qA",
"xchan_pubkey": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA18JB76lyP4zzL/y7BCej\neJnfZIWZNtM3MZvI1zEVMWmmwOS+u/yH8oPwyaDk4Y/tnj8GzMPj1lCGVRcd8EJa\nNrCMd50HODA5EsJtxpsOzRcILYjOcTtIAG1K4LtKqELi9ICAaFp0fNfa+Jf0eCek\nvPusx2/ORhy+o23hFoSMhL86o2gmaiRnmnA3Vz4ZMG92ieJEDMXt9IA1EkIqS4y5\nBPZfVPLD1pv8iivj+dtN1XjwplgjUbtxmU0/Ej808nHppscRIqx/XJ0XZU90oNGw\n/wYoK2EzJlPbRsAkwNqoFrAYlr5HPpn4BJ2ebFYQgWBUraD7HwS5atsQEaxGfO21\nlUP0+lDg9t3CXvudDj0UG1jiEKbVIGA+4aG0GN2DSC5AyRq/GRxqyay5W2vQbAZH\nyvxPGrZFO24I65g3pjhpjEsLqZ4ilTLQoLMs0drCIcRm5RxMUo4s/LMg16lT4cEk\n1qRtk2X0Sb1AMQQ2uRXiVtWz77QHMONEYkf6OW4SHbwcv5umvlv69NYEGfCcbgq0\nAV7U4/BWztUz/SWj4r194CG43I9I8dmaEx9CFA/XMePIAXQUuABfe1QMOR6IxLpq\nTHG1peZgHQKeGz4aSGrhQkZNNoOVNaZoIfcvopxcHDTZLigseEIaPPha4WFYoKPi\nUPbZ5o8gTLc750uzrnb2jwcCAwEAAQ==\n-----END PUBLIC KEY-----\n",
"xchan_photo_mimetype": "image/png",
"xchan_photo_l": "https://xyz.macgirvin.com/photo/profile/l/2",
"xchan_photo_m": "https://xyz.macgirvin.com/photo/profile/m/2",
"xchan_photo_s": "https://xyz.macgirvin.com/photo/profile/s/2",
"xchan_addr": "teller@xyz.macgirvin.com",
"xchan_url": "https://xyz.macgirvin.com/channel/teller",
"xchan_connurl": "https://xyz.macgirvin.com/poco/teller",
"xchan_follow": "https://xyz.macgirvin.com/follow?f=&url=%s",
"xchan_connpage": "",
"xchan_name": "Teller",
"xchan_network": "zot",
"xchan_instance_url": "",
"xchan_flags": "0",
"xchan_photo_date": "2016-10-19 01:26:50",
"xchan_name_date": "2016-01-02 21:16:26",
"xchan_hidden": "0",
"xchan_orphan": "0",
"xchan_censored": "0",
"xchan_selfcensored": "0",
"xchan_system": "0",
"xchan_pubforum": "0",
"xchan_deleted": "0"
},
{
"id": "12",
"uid": "2",
"gid": "1",
"xchan": "xuSMUYxw1djBB97qXsbrBN1nzJH_gFwQL6pS4zIy8fuusOfBxNlMiVb4h_q5tOEvpE7tYf1EsryjNciMuPIj5w",
"abook_id": "24",
"abook_account": "1",
"abook_channel": "2",
"abook_xchan": "xuSMUYxw1djBB97qXsbrBN1nzJH_gFwQL6pS4zIy8fuusOfBxNlMiVb4h_q5tOEvpE7tYf1EsryjNciMuPIj5w",
"abook_my_perms": "218555",
"abook_their_perms": "218555",
"abook_closeness": "80",
"abook_created": "2016-01-27 00:48:43",
"abook_updated": "2016-12-04 17:16:58",
"abook_connected": "2016-12-04 17:16:58",
"abook_dob": "0001-01-01 00:00:00",
"abook_flags": "0",
"abook_blocked": "0",
"abook_ignored": "0",
"abook_hidden": "0",
"abook_archived": "0",
"abook_pending": "0",
"abook_unconnected": "0",
"abook_self": "0",
"abook_feed": "0",
"abook_profile": "debb5236efb1626cfbad33ccb49892801e5f844aa04bf81f580cfa7d13204819",
"abook_incl": "",
"abook_excl": "",
"abook_instance": "",
"xchan_hash": "xuSMUYxw1djBB97qXsbrBN1nzJH_gFwQL6pS4zIy8fuusOfBxNlMiVb4h_q5tOEvpE7tYf1EsryjNciMuPIj5w",
"xchan_guid": "d5EMLlt1tHHZ0dANoA7B5Wq9UgXoWcFS9-gXOkL_AAejcPApoQRyxfHTuu8DoTbUaO-bYmX5HPuWuK9PHyqNmA",
"xchan_guid_sig": "CVWEMRPtzI1YcHfnnWHTuv3H964OAmSElgUfxMoX6RdQdxNpqb_POirpVuyP8s3W17mVCfO5V9IAjkg5iKcqCk6YcvOD_egmMy-AnM9TC1kKndQHw55CunD82Q8K_xBNSXkSROizcNkKh9DVLjJPFjW1AqtI4njkZ3EMgrWqnbFRM1qPToUoCY9zM3tEMHoAD9YX1zP90wl40LzfN-dtcNWpSBbiz9owou62uzLbN7mrCwKOMlXLjwwGswRnxIsEnb3O-FXOs8hs0mArKe9snq1-BKeD16LyzxgwlpVLElzIJZGEZGtMdIJgeRzKuBvPjsOIpQ1yAkuOpFJ3nGCM-IPOIIjAmyVl5zD3xPVcxxpZlJRn5fG1Y-gnqTgsrEQCA7M6XPWQdrdHU4akZfyUyFJDhv3uM-jon9VzrYTBw68R0WA-1Z8WafEHA4qh5OWAj85lUarwhr7iTiEckH51ypPCPs6VbT6Pw7yMaxfjFOcipashQagx0tfOlDhE5dQANOXKASFtH1J9-CZY2MQdLPQ6u54d5whuHKMGaJ0V68pnmZ2rOn7g344Ah2WCJrm17jj60QsRMorqRFj7GMdPIA1XB8Wrk88MuYOe3Dhyuu6ZWKI7YTWJS690ZVkKUqAiNHqj0W86DtaiPUc_mmGR0fHl4Gksnko3WmCFv9q2X2E",
"xchan_pubkey": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoj2xCJktBA8Ww7Hp+ZNL\nrNuQpo8UB/bfvRkIy+yua3xpF1TuXcnAH61kyRz8vXgOu/l2CyxQbIoaGslCV5Sy\n8JKeNXe+IilUdSSEjMIwCPfSPsYnMHsSnHWmPmclvJwEtQUKOZmW5mMuVBvXy7D2\njomFwc69AYphdyys6eQ7Dcn6+FRBiQbyMprZ5lxyVW+O4DuXVNa3ej2ebx0gCJZ4\ntTIlBoKwEey91dY+FyKVFjdwfNczpmL7LgmZXqcVx+MG3mYgibwdVMiXVj5X06cs\nV9hJ5Xi+Aklsv/UWJtjw9FVt7y9TLptnhh4Ra6T/MDmnBBIAkOR7P/X8cRv078MT\nl0IMsP0RJcDEtTLtwHFVtDs6p52KDFqclKWbqmxmxqV3OTPVYtArRGIzgnJi/5ur\nHRr5G6Cif7QY3UowsIOf78Qvy28LwSbdymgBAWwPPKIviXWxGO+9kMWdmPSUQrWy\nK0+7YA9P9fBUFfn9Hc+p8SJQmQ6OAqLwrDGiPSOlGaNrbEqwqLGgIpXwK+lEFcFJ\n3SPOjJRWdR2whlMxvpwX+39+H7dWN3vSa3Al4/Sq7qW8yW2rYwf+eGyp4Z0lRR+8\nJxFMCwZkSw5g14YdlikAPojv5V1c6KuA5ieg8G1hwyONV7A4JHPyEdPt0W0TZi6C\nCOVkPaC3xGrguETZpJfVpwUCAwEAAQ==\n-----END PUBLIC KEY-----\n",
"xchan_photo_mimetype": "image/png",
"xchan_photo_l": "https://xyz.macgirvin.com/photo/9da63aa910ea14e1501ee1a749d181a6-4",
"xchan_photo_m": "https://xyz.macgirvin.com/photo/9da63aa910ea14e1501ee1a749d181a6-5",
"xchan_photo_s": "https://xyz.macgirvin.com/photo/9da63aa910ea14e1501ee1a749d181a6-6",
"xchan_addr": "cloner@xyz.macgirvin.com",
"xchan_url": "http://abc.macgirvin.com/channel/cloner",
"xchan_connurl": "http://abc.macgirvin.com/poco/cloner",
"xchan_follow": "https://xyz.macgirvin.com/follow?f=&url=%s",
"xchan_connpage": "",
"xchan_name": "Karen",
"xchan_network": "zot",
"xchan_instance_url": "",
"xchan_flags": "0",
"xchan_photo_date": "2016-03-31 19:59:20",
"xchan_name_date": "2016-01-26 23:23:42",
"xchan_hidden": "0",
"xchan_orphan": "0",
"xchan_censored": "0",
"xchan_selfcensored": "0",
"xchan_system": "0",
"xchan_pubforum": "0",
"xchan_deleted": "0"
}
]
xchan
An xchan is a global location independent channel and is the primary record for a network identity. It may refer to channels on other websites, networks, or services.
GET /api/z/1.0/xchan
Required: one of [ address, hash, guid ] as GET parameters
Returns a portable xchan structure
Example: https://xyz.macgirvin.com/api/z/1.0/xchan?f=&address=mike@macgirvin.com
Returns:
{
"hash": "jr54M_y2l5NgHX5wBvP0KqWcAHuW23p1ld-6Vn63_pGTZklrI36LF8vUHMSKJMD8xzzkz7s2xxCx4-BOLNPaVA",
"guid": "sebQ-IC4rmFn9d9iu17m4BXO-kHuNutWo2ySjeV2SIW1LzksUkss12xVo3m3fykYxN5HMcc7gUZVYv26asx-Pg",
"guid_sig": "Llenlbl4zHo6-g4sa63MlQmTP5dRCrsPmXHHFmoCHG63BLq5CUZJRLS1vRrrr_MNxr7zob_Ykt_m5xPKe5H0_i4pDj-UdP8dPZqH2fqhhx00kuYL4YUMJ8gRr5eO17vsZQ3XxTcyKewtgeW0j7ytwMp6-hFVUx_Cq08MrXas429ZrjzaEwgTfxGnbgeQYQ0R5EXpHpEmoERnZx77VaEahftmdjAUx9R4YKAp13pGYadJOX5xnLfqofHQD8DyRHWeMJ4G1OfWPSOlXfRayrV_jhnFlZjMU7vOdQwHoCMoR5TFsRsHuzd-qepbvo3pzvQZRWnTNu6oPucgbf94p13QbalYRpBXKOxdTXJrGdESNhGvhtaZnpT9c1QVqC46jdfP0LOX2xrVdbvvG2JMWFv7XJUVjLSk_yjzY6or2VD4V6ztYcjpCi9d_WoNHruoxro_br1YO3KatySxJs-LQ7SOkQI60FpysfbphNyvYMkotwUFI59G08IGKTMu3-GPnV1wp7NOQD1yzJbGGEGSEEysmEP0SO9vnN45kp3MiqbffBGc1r4_YM4e7DPmqOGM94qksOcLOJk1HNESw2dQYWxWQTBXPfOJT6jW9_crGLMEOsZ3Jcss0XS9KzBUA2p_9osvvhUKuKXbNztqH0oZIWlg37FEVsDs_hUwUJpv2Ar09k4",
"pubkey": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA7QCwvuEIwCHjhjbpz3Oc\ntyei/Pz9nDksNbsc44Cm8jxYGMXsTPFXDZYCcCB5rcAhPPdZSlzaPkv4vPVcMIrw\n5cdX0tvbwa3rNTng6uFE7qkt15D3YCTkwF0Y9FVZiZ2Ko+G23QeBt9wqb9dlDN1d\nuPmu9BLYXIT/JXoBwf0vjIPFM9WBi5W/EHGaiuqw7lt0qI7zDGw77yO5yehKE4cu\n7dt3SakrXphL70LGiZh2XGoLg9Gmpz98t+gvPAUEotAJxIUqnoiTA8jlxoiQjeRK\nHlJkwMOGmRNPS33awPos0kcSxAywuBbh2X3aSqUMjcbE4cGJ++/13zoa6RUZRObC\nZnaLYJxqYBh13/N8SfH7d005hecDxWnoYXeYuuMeT3a2hV0J84ztkJX5OoxIwk7S\nWmvBq4+m66usn6LNL+p5IAcs93KbvOxxrjtQrzohBXc6+elfLVSQ1Rr9g5xbgpub\npSc+hvzbB6p0tleDRzwAy9X16NI4DYiTj4nkmVjigNo9v2VPnAle5zSam86eiYLO\nt2u9YRqysMLPKevNdj3CIvst+BaGGQONlQalRdIcq8Lin+BhuX+1TBgqyav4XD9K\nd+JHMb1aBk/rFLI9/f2S3BJ1XqpbjXz7AbYlaCwKiJ836+HS8PmLKxwVOnpLMbfH\nPYM8k83Lip4bEKIyAuf02qkCAwEAAQ==\n-----END PUBLIC KEY-----\n",
"photo_mimetype": "image/jpeg",
"photo_l": "https://xyz.macgirvin.com/photo/350b74555c04429148f2e12775f6c403-4",
"photo_m": "https://xyz.macgirvin.com/photo/350b74555c04429148f2e12775f6c403-5",
"photo_s": "https://xyz.macgirvin.com/photo/350b74555c04429148f2e12775f6c403-6",
"address": "mike@macgirvin.com",
"url": "https://macgirvin.com/channel/mike",
"connurl": "https://macgirvin.com/poco/mike",
"follow": "https://macgirvin.com/follow?f=&url=%s",
"connpage": "https://macgirvin.com/connect/mike",
"name": "Mike Macgirvin",
"network": "zot",
"instance_url": "",
"flags": "0",
"photo_date": "2012-12-06 05:06:11",
"name_date": "2012-12-06 04:59:13",
"hidden": "1",
"orphan": "0",
"censored": "0",
"selfcensored": "0",
"system": "0",
"pubforum": "0",
"deleted": "0"
}
item/update
Create or update an item (post, activity, webpage, etc.)
Usage: POST /api/z/1.0/item/update
Description: item/update posts an item (typically a conversation item or post, but can be any item) using form input.
Required:
- body
text/bbcode contents by default.
Optional:
- $_FILES['media']
uploaded media file to include with post
- title
title of post/item
- contact_allow
array of xchan.xchan_hash allowed to view this item
- group_allow
array of group.hash allowed to view this item
- contact_deny
array of xchan.xchan_hash not allowed to view this item
- group_deny
array of group.hash not allowed to view this item
- coord
geographic coordinates
- location
freefrom location
- expire
datetime this post will expire or be removed
- mimetype
mimetype if not text/bbcode
- parent
item.id of parent to this post (makes it a comment)
- parent_mid
alternate form of parent using message_id
- remote_xchan
xchan.xchan_hash of this message author if not the channel owner
- consensus
boolean set to true if this is a consensus or voting item (default false)
- nocomment
boolean set to true if comments are to be disabled (default false)
- origin
do not use this without reading the code
- namespace
persistent identity for a remote network or service
- remote_id
message_id of this resource on a remote network or service
- message_id
message_id of this item (leave unset to generate one)
- created
datetime of message creation
- post_id
existing item.id if this is an edit operation
- app
application or network name to display with item
- category
comma separated categories for this item
- webpage
item.page_type if not 0
- pagetitle
for webpage and design elements, the 'page name'
- layout_mid
item.mid of layout for this design element
- plink
permalink for this item if different than the default
- verb
activitystream verb for this item/activity
- obj_type
activitystream object type for this item/activity
Example:
curl -u mychannel:mypassword https://xyz.macgirvin.com/api/z/1.0/item/update -d body="hello world"
Returns:
{
"success": true,
"item_id": "2245",
"item": {
"id": "2245",
"mid": "14135cdecf6b8e3891224e4391748722114da6668eebbcb56fe4667b60b88249@xyz.macgirvin.com",
"aid": "1",
"uid": "2",
"parent": "2245",
"parent_mid": "14135cdecf6b8e3891224e4391748722114da6668eebbcb56fe4667b60b88249@xyz.macgirvin.com",
"thr_parent": "14135cdecf6b8e3891224e4391748722114da6668eebbcb56fe4667b60b88249@xyz.macgirvin.com",
"created": "2016-12-03 20:00:12",
"edited": "2016-12-03 20:00:12",
"expires": "0001-01-01 00:00:00",
"commented": "2016-12-03 20:00:12",
"received": "2016-12-03 20:00:12",
"changed": "2016-12-03 20:00:12",
"comments_closed": "0001-01-01 00:00:00",
"owner_xchan": "pgcJx1IQjuPkx8aI9qheJlBMZzJz-oTPjHy3h5pWlOVOriBO_cSiUhhqwhuZ74TYJ8_ECO3pPiRMWC0q8YPCQg",
"author_xchan": "pgcJx1IQjuPkx8aI9qheJlBMZzJz-oTPjHy3h5pWlOVOriBO_cSiUhhqwhuZ74TYJ8_ECO3pPiRMWC0q8YPCQg",
"source_xchan": "",
"mimetype": "text/bbcode",
"title": "",
"body": "hello world",
"html": "",
"app": "",
"lang": "",
"revision": "0",
"verb": "http://activitystrea.ms/schema/1.0/post",
"obj_type": "http://activitystrea.ms/schema/1.0/note",
"obj": "",
"tgt_type": "",
"target": "",
"layout_mid": "",
"postopts": "",
"route": "",
"llink": "https://xyz.macgirvin.com/display/14135cdecf6b8e3891224e4391748722114da6668eebbcb56fe4667b60b88249@xyz.macgirvin.com",
"plink": "https://xyz.macgirvin.com/channel/mychannel/?f=&mid=14135cdecf6b8e3891224e4391748722114da6668eebbcb56fe4667b60b88249@xyz.macgirvin.com",
"resource_id": "",
"resource_type": "",
"attach": "",
"sig": "sa4TOQNfHtV13HDZ1tuQGWNBpZp-nWhT2GMrZEmelXxa_IvEepD2SEsCTWOBqM8OKPJLfNy8_i-ORXjrOIIgAa_aT8cw5vka7Q0C8L9eEb_LegwQ_BtH0CXO5uT30e_8uowkwzh6kmlVg1ntD8QqrGgD5jTET_fMQOIw4gQUBh40GDG9RB4QnPp_MKsgemGrADnRk2vHO7-bR32yQ0JI-8G-eyeqGaaJmIwkHoi0vXsfjZtU7ijSLuKEBWboNjKEDU89-vQ1c5Kh1r0pmjiDk-a5JzZTYShpuhVA-vQgEcADA7wkf4lJZCYNwu3FRwHTvhSMdF0nmyv3aPFglQDky38-SAXZyQSvd7qlABHGCVVDmYrYaiq7Dh4rRENbAUf-UJFHPCVB7NRg34R8HIqmOKq1Su99bIWaoI2zuAQEVma9wLqMoFsluFhxX58KeVtlCZlro7tZ6z619-dthS_fwt0cL_2dZ3QwjG1P36Q4Y4KrCTpntn9ot5osh-HjVQ01h1I9yNCj6XPgYJ8Im3KT_G4hmMDFM7H9RUrYLl2o9XYyiS2nRrf4aJHa0UweBlAY4zcQG34bw2AMGCY53mwsSArf4Hs3rKu5GrGphuwYX0lHa7XEKMglwBWPWHI49q7-oNWr7aWwn1FnfaMfl4cQppCMtKESMNRKm_nb9Dsh5e0",
"diaspora_meta": "",
"location": "",
"coord": "",
"public_policy": "",
"comment_policy": "contacts",
"allow_cid": "",
"allow_gid": "",
"deny_cid": "",
"deny_gid": "",
"item_restrict": "0",
"item_flags": "0",
"item_private": "0",
"item_origin": "1",
"item_unseen": "0",
"item_starred": "0",
"item_uplink": "0",
"item_consensus": "0",
"item_wall": "1",
"item_thread_top": "1",
"item_notshown": "0",
"item_nsfw": "0",
"item_relay": "0",
"item_mentionsme": "0",
"item_nocomment": "0",
"item_obscured": "0",
"item_verified": "1",
"item_retained": "0",
"item_rss": "0",
"item_deleted": "0",
"item_type": "0",
"item_hidden": "0",
"item_unpublished": "0",
"item_delayed": "0",
"item_pending_remove": "0",
"item_blocked": "0"
}
}
item/full
Get all data associated with an item
abook
Connections
abconfig
Connection metadata (such as permissions)
perm_allowed
Check a permission for a given xchan
Hooks
Hooks allow plugins/addons to ‘hook’ into the code in many places and change the behaviour or otherwise perform independent actions when an activity takes place or certain data structures are accessed. There are many hooks that allow you to hook into the software at almost any point and do something other than what is intended by default. Two variables are passed to these hooks. The first is the app structure, which contains details about the overall state of the page request as we build the resulting page. The second is unique to the specific hook being called and provides specific details about what is happening in the software at the time the hook is called.
Created index of all hooks and the files they call
module_mod_aftercontent General hook for each module, executed after mod_content(). Replace ‘module’ with the name of the module, e.g. ‘photos_mod_aftercontent’.
module_mod_content General hook for any module, executed before mod_content(). Replace ‘module’ with the module name, e.g. ‘photos_mod_content’.
module_mod_init General hook for any module, executed before mod_init(). Replace ‘module’ with the module name, e.g. ‘photos_mod_init’.
module_mod_post General hook for any module, executed before mod_post(). Replace ‘module’ with the name of the module, e.g. ‘photos_mod_post’.
about_hook Called from the siteinfo page
accept_follow Called when a connection is accepted (friend request)
account_downgrade Called when an account has expired, indicating a possible downgrade to the ‘basic’ class of service
account_settings Called when the account settings form is created
account_settings_post Called when posting from the account settings form
activity_filter Called when creating the list of filters for the network page
activity_mapper Called when determining the activity type for the transfer.
activity_decode_mapper Called when the activity type for the transfer is determined.
activity_obj_mapper Called when the object type for the transfer is determined.
activity_obj_decode_mapper Is called when the object type for the transfer is determined.
activity_order Called when generating the list of order options for the network page
addon_app_installed_filter Called when determining whether an addon_app isinstalled
activity_received Called when an activity (post, comment, like, etc.) has been received from a Nomad source
admin_aside Is called when the sidebar widget of the administration page is created
affinity_labels Is used to generate alternative labels for the affinity slider.
api_perm_is_allowed Called when perm_is_allowed() is executed by an API call.
app_destroy Called when an app is deleted.
app_installed_filter Called when it is determined whether an app isinstalled
app_menu Called when the app_menu dropdown is created (may be deprecated)
attach_delete Called when attachments are deleted from the attach table
atom_author Called when an author or owner element is created for an Atom ActivityStream feed
atom_entry Called when generating each entry of an Atom ActivityStream feed
atom_feed Called when an Atom ActivityStreams feed is generated
atom_feed_end Called when the generation of an Atom ActivityStreams feed is complete
attach_upload_file Called when a file is uploaded
authenticate Can provide alternative authentication mechanisms
author_is_pmable Called from the thread's action menu to determine if we can send a private email to the author of the post
bb2diaspora Called when converting bbcode to Markdown
bbcode Called at the end of the conversion from bbcode to HTML
bbcode_filter Called at the beginning of the conversion from bbcode to HTML
bb_translate_video Called when extracting embedded services from bbcode video elements (rarely used)
build_pagehead Called when the HTML page header is created
can_comment_on_post Called when deciding whether or not to display a comment field for a post
change_channel Called when you log in to a channel (either during login or afterwards via the channel manager)
channel_remove Called when a channel is removed
channel_links Is called when the link is generated: HTTP header for a channel
channel_settings Called when the channel settings page is displayed
chat_message Called to create a chat message.
chat_post Called when a chat message has been posted.
check_account_email Checks the email specified during account registration
check_account_invite Validation of an invitation code when using website invitations check_account_password Used to check account passwords (minimum length, inclusion of character sets, etc.) check_channelallowed Used to override or bypass black and white channel block lists.
check_siteallowed Is used to override or bypass the black/white block lists for websites.
collect_public_recipients Used to create a list of recipients to send a public message to.
comment_buttons Called when the comment edit buttons are displayed.
comments_are_now_closed Called when deciding whether or not to display a comment box for a post
connect_premium Called when a connection to a premium channel is established
connection_remove Called when a connection is deleted/removed
connector_settings Called when the page with the features/addon settings is called up
construct_page General hook for providing content for specific page areas. Is called when the Comanche page is created.
contact_block_end Called when the ‘Connections’ widget is created in the sidebar
contact_edit Called when editing a connection via connedit
contact_edit_post Is called when a post is sent to connedit contact_selection_options Deprecated/unused
content_security_policy Called before the Content-Security-Policy header is output
conversation_start Called at the beginning of the rendering of a conversation (message or message collection or stream)
cover_photo_content_end Called after a cover photo has been uploaded
create_identity Called when a channel is created
cron Called when a scheduled task (poller) is executed
cron_daily Called when daily scheduled tasks are executed
cron_weekly Called when weekly scheduled tasks are executed
crypto_methods Called when a list of crypto algorithms is created in the locally preferred order
daemon_addon Called when the extensible background daemon is called
daemon_master_release Called at the beginning of the processing of \Zotlabs\Daemon\Master::Release()
directory_item Called when creating a directory listing for display
discover_channel_webfinger Called when a webfinger lookup is performed
display_item Called for each element that is displayed in a conversation thread
display_settings Called by the settings module when the ‘display settings’ section is displayed
display_settings_post Called when a post from the ‘display settings’ form of the settings module is displayed
donate_contributors Called by the ‘donate’ addon when a list of donation recipients is created
donate_plugin is called by the ‘donate’ addon
donate_sponsors Called by the ‘donate’ addon
dreport_is_storable is called before saving a Dreport record to determine if it should be saved
dreport_process is called for each valid delivery report
dropdown_extras Add additional items to the dropdown menu when item/threads are displayed.
drop_item is called when an ‘item’ is removed
encode_object is called when an object is encoded for transmission.
enotify is called before each notification
enotify_mail is called when a notification email is sent
enotify_store is called when a notification data record is saved
enotify_store_end is called after a notification record has been saved
event_created is called when an event record is created
event_store_event is called when an event record is created or updated
event_updated is called when an event record is changed
externals_url_select is called when a list of random websites from which to retrieve public posts is created
feature_enabled is called when ‘feature_enabled()’ is used
feature_settings is called from the settings page when visiting ‘addon/feature settings’
feature_settings_post is called from the settings page when posting from ‘addon/feature settings’
fetch_and_store is called to enable filtering of ‘decrypted’ elements before saving.
file_thumbnail is called when creating thumbnails for the cloud page in ‘show tiles’ mode
follow is called when a follow operation takes place
follow_from_feed is called when a follow operation takes place in an RSS feed
follow_allow is called before the results of a follow operation are saved
gender_selector is called when the ‘Gender’ drop-down list is created (extended profile)
gender_selector_min is called when the ‘Gender’ drop-down list is created (normal profile)
generate_map is called to generate the HTML code for displaying a location on the map by coordinates
generate_named_map is called to generate the HTML file for displaying a map location by text
get_all_api_perms Called when the permissions for API uses are retrieved
get_all_perms is called when get_all_perms() is used get_best_language is called when the preferred language for the page is selected get_default_export_sections Called to get the default list of function data groups to be exported in identity_basic_export()
get_features Called when get_features() is called
get_photo Called when photo content (except profile photos) is retrieved in mod_photo
get_profile_photo Called when the content of the local profile photo is retrieved in mod_photo
get_role_perms Called when get_role_perms() is called to get permissions for named permission roles
global_permissions Called when the global permissions list is created
home_content Called by mod_home to replace the content of the home page
home_init Called by the home_init() function of the home page
hostxrd Called when generating .well-known/hosts-meta for ‘old webfinger’ (used by the Diaspora protocol)
html2bb_video Called when html2bbcode translation is used to handle embedded media
html2bbcode Called when using the html2bbcode translation
identity_basic_export Called when the basic information of a channel is exported for backup or transfer.
import_author_xchan Called when searching for an author of a post with xchan_hash to make sure they have an xchan entry on our website
import_channel Called when a channel is imported from a file or API source
import_directory_profile Called when processing the delivery of a profile structure from an external source (usually for storage in directories)
import_xchan Called when processing the result of zot_finger() to save the result
item_photo_menu Called when the list of actions associated with a displayed conversation item is generated
item_store Called when item_store() stores a record of type item
item_stored Called after item_store() has stored a record of type item in the database.
item_custom Is called before item_store() saves a data record of the type item (so that addons can process ITEM_TYPE_CUSTOM elements).
item_store_update Called when item_store_update() is called to update a stored item.
item_stored_update Called after item_store_update() has updated astored item.
item_translate Called by item_store and item_store_update after the language of the item has been automatically recognised.
jot_networks Called to generate the list of additional post plugins to be activated from the ACL form
jot_tool Obsolete and possibly superfluous. Enables action buttons to be added to the post editor.
jot_tpl_filter Called to filter template variables before replacing them in jot.tpl.
jot_header_tpl_filter Called to filter template variables before replacing them in jot_header.tpl.
legal_webbie Called to validate a channel address
legal_webbie_text Provides an explanation of the text/character restrictions for legal_webbie()
load_pdl Called when we load a PDL file or description
local_dir_update Called when a directory update is processed by a channel on the directory server
location_move Called when a UNO channel has been notified of a new location (indicating a move and not a clone)
logged Called when authentication was successful in any way
Logger Called when an entry is made in the application's log file
logging_out Called when loggingout
login_hook Called when the login form is generated
magic_auth Called when processing a magic-auth sequence
markdown_to_bb Called when processing the Markdown conversion
match_webfinger_location Called when processing webfinger requests
magic_auth_openid_success Called when a magic-auth was successful due to openid credentials
magic_auth_success Called when a magic-auth was successful
main_slider Called when the affinity tool is generated
marital_selector Called when the selection list for the drop-down menu of the ‘Marital status’ profile is created (extended profile)
marital_selector_min Called when the selection list for the ‘Marital status’ drop-down profile is created (normal profile)
module_loaded Is called when a module has been successfully localised for a URL request on the server.
mood_verbs Called when the list of moods is created nav Called when the navigation bar is created
network_content_init Called when loading the content for the network page
network_ping Called when a ping request is made network_to_name Deprecated
notifier_end Called when a delivery loop is completed
notifier_hub Called when a hub has been delivered
notifier_normal Called when the notifier is called for a ‘normal’ delivery
notifier_process Called when the notifier processes a message/event
obj_verbs Called when the list of verbs available for the ‘things’ profile is created.
oembed_action Called when deciding whether to filter, block or approve an oembed url
oembed_probe Called when a search for Oembed content is performed.
other_encapsulate Called when encrypting content for which the algorithm is unknown (see also crypto_methods)
other_unencapsulate Called when decrypting content for which the algorithm is unknown (see also crypto_methods)
page_content_top Called when we generate a web page (before calling the module content function)
page_end Called after we have generated the page content
page_header Called when the navigation bar is generated
page_meta Called when generating the metadata in the page header.
parse_atom Called when an Atom/RSS feed element is parsed.
parse_link Is called when a URL is queried to generate a post from it
pdl_selector Called when creating a layout selection in a form
perm_is_allowed Called during perm_is_allowed() to determine whether authorisation is permitted for this channel and observer
permissions_create Called when a book entry (connection) is created
permissions_update Is called when an authorisation update is transferred
permit_hook Called before a registered hook is actually executed to determine whether it should be allowed or blocked
personal_xrd Called when generating the personal XRD for ‘old webfinger’ (Diaspora)
photo_post_end Called after a photo has been uploaded
photo_upload_begin Called when an attempt is made to upload a photo
photo_upload_end Called when a photo upload has been processed
photo_upload_file Called to generate alternative file names for an upload
photo_upload_form Called when a photo upload form is generated
photo_view_filter Called before the data is passed to the photo_view template
poke_verbs Called when creating the list of actions for the ‘poke’ module
post_local Called when an article has been set on this computer via mod/item.php (also via API)
post_local_end Is called when a local post process has been completed
post_local_start Is called when a local post process begins post_mail Called when a mail message has been created
post_mail_end Called when a mail message has been delivered
post_remote Called when an activity arrives from another location
post_remote_end Called after a remote post has been processed
post_remote_update Called when processing a remote post that includes an edit or update
post_remote_update_end Called after processing a remote post that included an edit or update
prepare_body Is called when the HTML code for a displayed conversation object is generated.
prepare_body_final Called after the HTML for a displayed conversation item has been generated
prepare_body_init Called before the HTML for a displayed conversation element is generated
privacygroup_extras Called before generating the HTML for the privacy group editing options
privacygroup_extras_delete Called after the privacy group has been deleted.
privacygroup_extras_post Is called when the form for editing the privacy group is sent.
proc_run Called when PHP sub-processes are called process_channel_sync_delivery Called when a ‘sync package’ with structure and table updates is received from a channel clone.
profile_advanced Called when an advanced profile page is generated
profile_edit Called when editing a profile
profile_photo_content_end Called when a profile photo is changed
profile_post Called when an edited profile is posted
profile_sidebar Is called up when the ‘channel sidebar’ or the mini profile is created
profile_sidebar_enter Called before the ‘channel sidebar’ or mini-profile is created
queue_deliver Called when a message is delivered in the queue
register_account Called when an account has been created
render_location Called to create an inactive inline map
replace_macros Called before the template processor is called
reverse_magic_auth Called before calling reverse magic auth to send you to your own website so you can authenticate on that website
settings_account Called when the account settings form is created
settings_form Called when creating the channel settings form
settings_post Called when posting from the channel settings form
sexpref_selector Called when creating a drop-down menu for sexual preferences (advanced profile)
sexpref_selector_min Called when a drop-down list of sexual preferences is created (normal profile)
smilie Called when translating emoticons
status_editor Called when the status_editor is created.
stream_item Called for each item that is rendered for display via conversation()
system_app_installed_filter Called when it is determined whether a system app isinstalled.
tagged Called when a delivery is processed that results in you being tagged
thumbnail Called when creating thumbnails for the cloud storage tile view
update_unseen Called before automatically tagging programmes loaded in the browser that have been viewed
validate_channelname Used to validate the names used by a channel
webfinger Called when visiting the webfinger service (RFC7033)
well_known Called when accessing the special ‘.well-known’ site addresses
wiki_preprocess Called before markdown/bcode processors are executed for wiki pages
zot_best_algorithm Called when negotiating encryption algorithms with remote sites
zid Called when the observer's zid is added to a URL
zid_init Called when authenticating a visitor who has used zid
zot_finger Called when a Nomad info packet has been requested (this is our web finger detection mechanism)
To-be-organized information
Here is how you can join us.
First, get yourself a working git package on the system where you will be doing development.
Create your own github account.
You may fork/clone the Red repository from https://framagit.org/hubzilla/core.git.
Follow the instructions provided here: http://help.github.com/fork-a-repo/ to create and use your own tracking fork on github
Then go to your github page and create a "Pull request" when you are ready to notify us to merge your work.
Important
Please pull in any changes from the project repository and merge them with your work before issuing a pull request. We reserve the right to reject any patch which results in a large number of merge conflicts. This is especially true in the case of language translations - where we may not be able to understand the subtle differences between conflicting versions.
Also - test your changes. Don't assume that a simple fix won't break something else. If possible get an experienced Red developer to review the code.
How to theme Hubzilla
This is a short documentation on what I found while trying to modify Hubzilla's appearance.
First, you'll need to create a new theme. This is in /view/theme, and I chose to copy 'redbasic' since it's the only available for now. Let's assume I named it .
Oh, and don't forget to rename the _init function in /php/theme.php to be _init() instead of redbasic_init().
At that point, if you need to add javascript or css files, add them to /js or /css, and then "register" them in _init()
through head_add_js('file.js')
and head_add_css('file.css')
.
Now you'll probably want to alter a template. These can be found in in /view/tpl OR view//tpl. All you should have to do is copy whatever you want to tweak from the first place to your theme's own tpl directory.
We're pretty relaxed when it comes to developers. We don't have a lot of rules. Some of us are over-worked and if you want to help we're happy to let you help. That said, attention to a few guidelines will make the process smoother and make it easier to work together. We have developers from across the globe with different abilities and different cultural backgrounds and different levels of patience. Our primary rule is to respect others. Sometimes this is hard and sometimes we have very different opinions of how things should work, but if everybody makes an effort, we'll get along just fine.
Here is how you can join us.
First, get yourself a working git package on the system where you will be doing development.
Create your own github account.
You may fork/clone the hubzilla repository from https://framagit.org/hubzilla/core.git
Follow the instructions provided here: http://help.github.com/fork-a-repo/]http://help.github.com/fork-a-repo/ to create and use your own tracking fork on github
Then go to your github page and create a "Pull request" when you are ready to notify us to merge your work.
Important
Please pull in any changes from the project repository and merge them with your work before issuing a pull request. We reserve the right to reject any patch which results in a large number of merge conflicts. This is especially true in the case of language translations - where we may not be able to understand the subtle differences between conflicting versions.
Also - test your changes. Don't assume that a simple fix won't break something else. If possible get an experienced Red developer to review the code.
Further documentation can be found at the Github wiki pages at: https://github.com/friendica/red/wiki
Concensus Building
Code changes which fix an obvious bug are pretty straight-forward. For instance if you click "Save" and the thing you're trying to save isn't saved, it's fairly obvious what the intended behaviour should be. Often when developing feature requests, it may affect large numbers of community members and it's possible that other members of the community won't agree with the need for the feature, or with your proposed implementation. They may not see something as a bug or a desirable feature.
We encourage consensus building within the community when it comes to any feature which might be considered controversial or where there isn't unanimous decision that the proposed feature is the correct way to accomplish the task. The first place to pitch your ideas is to Channel One]. Others may have some input or be able to point out facets of your concept which might be problematic in our environment. But also, you may encounter opposition to your plan. This doesn't mean you should stop and/or ignore the feature. Listen to the concerns of others and try and work through any implementation issues.
There are places where opposition cannot be resolved. In these cases, please consider making your feature optional or non-default behaviour that must be specifically enabled. This technique can often be used when a feature has significant but less than unanimous support. Those who desire the feature can turn it on and those who don't want it - will leave it turned off.
If a feature uses other networks or websites and or is only seen as desirable by a small minority of the community, consider making the functionality available via an addon or plugin. Once again, those who don't desire the feature won't need to install it. Plugins are relatively easy to create and "hooks" can be easily added or modified if the current hooks do not do what is needed to allow your plugin to work. ``