Compare commits

...

10 commits

27 changed files with 4154 additions and 928 deletions

View file

@ -1,7 +1,7 @@
blank_issues_enabled: false
contact_links:
- name: General Matrix Room
- url: https://matrix.to/#/#telodendria-general:bancino.net
- about: |
url: "https://matrix.to/#/#telodendria-general:bancino.net"
about: |
General discussion on Telodendria happens in this Matrix room. You
may get quicker feedback from there.

View file

@ -0,0 +1,25 @@
---
Please review the developer certificate of origin:
1. The contribution was created in whole or in part by me, and I have
the right to submit it under the open source licenses of the
Telodendria project; or
1. The contribution is based upon a previous work that, to the best of
my knowledge, is covered under an appropriate open source license and
I have the right under that license to submit that work with
modifications, whether created in whole or in part by me, under the
Telodendria project license; or
1. The contribution was provided directly to me by some other person
who certified (1), (2), or (3), and I have not modified it.
1. I understand and agree that this project and the contribution are
made public and that a record of the contribution—including all
personal information I submit with it—is maintained indefinitely
and may be redistributed consistent with this project or the open
source licenses involved.
- [ ] I have read the Telodendria Project development certificate of
origin, and I certify that I have permission to submit this patch
under the conditions specified in it.

View file

@ -196,7 +196,7 @@ HttpRouterRoute(HttpRouter * router, char *path, void *args, void **ret)
char *pathPart;
char *tmp;
HttpRouteFunc *exec = NULL;
Array *matches;
Array *matches = NULL;
size_t i;
int retval;
@ -254,14 +254,18 @@ HttpRouterRoute(HttpRouter * router, char *path, void *args, void **ret)
{
/* pmatch[0] is the whole string, not the first
* subexpression */
char * substr;
regmatch_t cpmatch;
for (i = 1; i < REG_MAX_SUB; i++)
{
cpmatch = pmatch[i];
substr = StrSubstr(pathPart, cpmatch.rm_so, cpmatch.rm_eo);
if (pmatch[i].rm_so == -1)
{
break;
}
ArrayAdd(matches, StrSubstr(pathPart, pmatch[i].rm_so, pmatch[i].rm_eo));
ArrayAdd(matches, substr);
}
}
}

View file

@ -147,7 +147,6 @@ StrSubstr(const char *inStr, size_t start, size_t end)
}
len = end - start;
outStr = Malloc(len + 1);
if (!outStr)
{

163
docs/CONTRIBUTING.md Normal file
View file

@ -0,0 +1,163 @@
# Contributing
Telodendria is a fully open source project. As such, it welcomes
contributions. There are many ways you can contribute, and any way you
can is greatly appreciated. This document details the ways you can
contribute, and how to go about contributing.
## Sponsoring Telodendria
If you would like to sponsor Telodendria, see the
[Sponsorship](../README.md#sponsorship) section on the main project
page. Donations of any size are greatly appreciated.
## Reporting Issues
An important way to get involved is to just report issues you find with
Telodendria during experimentation or normal use. To report an issue,
go to [Issues](/Telodendria/telodendria/issues) &rightarrow;
[New Issue](/Telodendria/telodendria/issues/new/choose) and follow the
instructions.
> **Note:** GitHub issues are not accepted. Issues may only be
> submitted to the official [Gitea](https://git.telodendria.io)
> instance.
### Feature Requests
Feature requests are allowed, but note that they are low-priority in
comparison to existing issues and features. That being said, don't
hesitate to submit feature requests. Just select the "Feature Request"
option when submitting an issue.
## Developing
If you want to write code for Telodendria, either to fix an issue or
add a new feature, you're in the right place. Please follow all the
guidelines in this document to ensure the contribution workflow goes
as smoothly as possible.
### Who can develop Telodendria?
Everyone is welcome to contribute code to Telodendria, provided that
they are willing to license their contributions under the same license
as the project itself.
The primary language used to write Telodendria code is ANSI C. Other
languages you'll find in the Telodendria repository include shell
scripts, `mdoc`, a little bit of HTML and CSS, and `Makefiles`.
Experience with any of these is preferred, but if you want to use
Telodendria to learn, that's okay too! Telodendria's code base should
hopefully be a good learning tool, and if you are serious about
submitting quality work, we'll guide you through the process and
offer suggestions.
### What do I need?
You'll need a couple of things to develop Telodendria:
- A Unix-like operating system that provides standard POSIX behavior,
or the Windows Subsystem for Linux (WSL), Cygwin, or Msys2 if you are
running Windows.
- A C compiler capable of compiling ANSI C89 code (pretty much all of
them do&mdash;pick your favorite, and if you find it doesn't work,
open an issue!).
- `make` for building the project.
- `git` for managing your changes.
Optionally, you may also find these tools helpful:
- `indent` for formatting code.
- `valgrind` for debugging particularly nasty issues.
### Getting The Code
Telodendria is developed using Git. The easiest way to contribute
changes is to fork the main repository, and then creating a pull
request to ask us to pull your changes into our repo.
1. If you don't have an account on the
[Gitea instance](https://git.telodendria.io), create one and sign in.
1. Fork this repository.
1. In your development environment, clone your fork:
```shell
git clone https://git.telodendria.io/[YOUR_USERNAME]/telodendria.git
cd telodendria
```
Please base your changes on the `master` branch. If you need help
getting started with Git, that is beyond the scope of this
document, but you can find many good tutorials on the web.
### Building &amp; Running
Telodendria uses the `make` build system.
**TODO:** Update this section before #19 is closed. Provide quick
make, run, and install directions (maybe just redirect to Porting for
install directions), then list all the `make` recipes. Shouldn't be
as many as were in `td`.
### Pull Requests
> **Note:** Telodendria does not accept GitHub pull requests at this
> time. Please submit your pull requests via Gitea.
Telodendria follows the standard pull request procedures. Once you have
made your changes, committed them, and pushed to your fork, you should
be able to open a pull request on the main repository. When you do, you
will be prompted to write a description. Be sure to include the
related issue that you are closing in your description.
### Code Style
In general, these are the conventions used by the code base. This
guide may be slightly outdated or subject to change, but it should be
a good start. The source code itself is always the absolute source of
truth, so as long as you make your code look like the code surrounding
it, you should be fine.
- All function, enumeration, structure, and header names are
`CamelCase`. This is preferred to `snake_case` because it is more
compact.
- All variable names are `lowerCamelCase`. This is preferred to
`snake_case` because it is more compact. One exception to this rule is
if a variable name, such as a member of a struct, directly represents
a JSON key in an object specified by the Matrix specification, which
may be in `snake_case`.
- Enumerations and structures are always `typedef`-ed to their same
name. The `typedef` should occur in the public API header, and the
actual declaration should live in the implementation file, unless
the enumeration or structure is intended to be made fully public.
- A feature of the code base lives in a single C source file that has a
matching header. The header file should only export public symbols;
everything else in the C source should be static.
- Except where absolutely necessary, global variables are forbidden
to prevent problems with threads and whatnot. Every variable a
function needs should be passed to it either through a structure, or
as a separate argument.
- Anywhere that C allows curly braces to be optional, there still must
be curly braces. This makes it easier to read the code by making it
less ambiguous, and it makes it easier to add on to the code later.
As far as actually formatting the code goes, such as where to put
brackets, and whether or not to use tabs or spaces, use `indent` to
take care of that. The repository contains a `.indent.pro` that should
automatically be loaded by `indent` to set the correct rules. If you
don't have a working `indent`, then just indicate in your pull
request that I should run my `indent` on the code.
### Documentation
This project places a strong emphasis on documentation. Well-documented
code is fundamental to a successful project, so when you are writing
code, please also make sure that it is documented appropriately.
- If you are adding a header, make sure you add the necessary comments
detailing the header and the functions in it.
- If you are adding a function, make sure you add the necessary
comments to the appropriate header.
If your pull request does not also include proper documentation, it
will likely be rejected.

246
docs/user/config.md Normal file
View file

@ -0,0 +1,246 @@
# Configuration
Telodendria is designed to be configurable. It is configured using
JSON, which is intended to be submitted to the [Administrator
API](admin/README.md). This document details Telodendria's configuration
JSON format, which is used in both the administrator API and on-disk
in the database. The configuration file on the disk in the databsae
is `config.json`, though that file should not be edited by hand.
Use the API described in
[Administrator API &rightarrow; Configuration](admin/config.md).
## JSON Format
Telodendria's configuration is just a JSON object in the standard
key-value form:
```json
{
"serverName": "telodendria.io",
"listen": [
{
"port": 8008,
"tls": false
}
]
/* ... */
}
```
Some keys, called *directives* in this document, have values that are
objects themselves.
## Directives
Here are the top-level directives:
- **listen:** `Array`
An array of listener description objects. Telodendria supports
listening on multiple ports, and each port is configured
independently of the others. A listener object looks like this:
- **port:** `integer`
The port to listen on. Telodendria will bind to all interfaces,
so it is recommended to configure your firewall to only allow
access on the desired interfaces. Note that Telodendria offers all
APIs over each port, including the administrator APIs; there is no
way to control which APIs are made available over which ports. If
this is a concern, a reverse-proxy such as `relayd` can be placed
in front of Telodendria to block access to undesired APIs.
- **tls:** `Object|null|false`
Telodendria can be compiled with TLS support. If it is, then a
particular listener can be set to use TLS for connections. If
**tls** is not `null` or `false`, then it can be an object with
the following directives:
- **cert:** `String`
The full path&mdash;or path relative to the data
directory&mdash;of the certificate file to load. The certificate
file should be in the format expected by the platform's TLS
library.
- **key:** `String`
Same as **cert**, but this should be the private key that matches
the certificate being used.
- **threads:** `Integer`
How many worker threads to spin up to handle requests for this
listener. This should generally be less than the total CPU core
count, to prevent overloading the system. The most efficient number
of threads ultimately depends on the configuration of the machine
running Telodendria, so you may just have to play around with
different values here to see which gives the best performance.
Note that this can be set as low as 0; in that case, the listener
will never respond to requests. Each listener needs to have at
least one thread to be useful. Also note that Telodendria may spin
up additional threads for background work, so the actual total
thread count at any given time may exceed the sum of threads
specified in the configuration.
This directive is optional. The default value is `4` in the upstream
code, but your software distribution may have patched this to be
different.
- **maxConnections:** `Integer`
The maximum number of simultanious connections to allow to the
daemon. This option prevents the daemon from allocating large
amounts of memory in the event that it undergoes a denial of
service attack. It is optional, defaults to `32`, and typically
does not need to be adjusted.
- **serverName:** `String`
Configure the domain name of your homeserver. Note that Matrix
servers cannot be migrated to other domains, so once this is set,
it should never change unless you want unexpected things to happen
or you want to start over. **serverName** should be a DNS name that
can be publicly resolved. This directive is required.
- **baseUrl:** `String`
Set the server's base URL. **baseUrl** should be a valid URL,
complete with the protocol. It does not need to be the same as the
server name; in fact, it is common for a subdomain of the server name
to be the base URL for the Matrix homeserver.
This URL is the URL at which Matrix clients will connect to the
server, and is thus served as a part of the `.well-known`
manifest.
This directive is optional. If unspecified, it is automatically
deduced from the server name.
- **identityServer:** `String`
The identity server that clients should use to perform identity
lookups. **identityServer** folows the same rules as **baseUrl**.
It also is optional, and is set to be the same as the **baseUrl**
if left unspecified.
- **runAs:** `Object`
The effective Unix user and group to drop to after binding to the
socket and completing any setup that may potentially require
elevated privileges. This directive only takes effect if
Telodendria is started as the root user, and is used as a security
mechanism. If this option is set and Telodendria is started as a
non-privileged user, then a warning is printed to the log if that
user and group do not match what's specified here. This directive
is optional, but should be used as a sanity check even if not
running as `root`, just to make sure you have your permissions
working properly.
This directive takes an object with the following directives:
- **uid:** `String`
The Unix username to switch to. If **runAs** is specified, this
directive is required.
- **gid:** `String`
The Unix group to switch to. This directive is optional; if left
unspecified, then the value of **uid** is copied.
- **federation:** `Boolean`
Whether or not to enable federation with other Matrix homeservers.
Matrix by its very nature is a federated protocol, but if you just
want to rn your own internal chat server with no contact with the
outside, then you can use this option to disable federation. It is
highly recommended to set this to `true`, however, if you wish to
be able to communicate with users on other Matrix servers. This
directive is required.
- **registration:** `Boolean`
Whether or not to enable new user registration or not. For security
and anti-spam reasons, you can set this to `false`. If you do, you
can still allow only certain users to be registered using
registration tokens, which can be managed via the administrator API.
This directive is required.
In an ideal world, everyone would run their own Matrix homeserver,
so no public registration would ever be required. Unfortunately,
not everyone has the means to run their own homeserver, especially
because of the fact that IPv4 addresses are becoming increasingly
hard to come by. If you would like to provide a service to those
that are unable to run their homeserver, then set this to `true`,
thereby allowing anyone to create an account.
Telodendria *should* be capable of handling a large amount of users
without difficulty, but it is targetted at smaller deployments.
- **log:** `Object`
The logging configuration. Telodendria uses its own logging
facility, which can output logs to standard output, a file, or the
syslog. This directive is required, and it takes an object with the
following directives:
- **output:** `Enum`
The log output destination. This can either be `stdout`, `file`,
or `syslog`. If set to `file`, Telodendria will log to
`telodendria.log` inside the data directory.
- **level:** `Enum`
The level of messages to log. Each level shows all the levels above
it. The levels are as follows:
- `error`
- `warning`
- `notice`
- `message`
- `debug`
For example, setting the level to `error` will show only errors,
while setting the level to `warning` will show both warnings
*and* errors. The `debug` level shows all messages.
- **timestampFormat:** `Enum`
If you want to customize the timestamp format shown in the log,
or disable it altogether, you can do so via this option. Acceptable
values are `none`, `default`, or a formatter string as described
by your system's `strftime()` documentation. This option only
applies if **log** is `stdout` or `file`.
- **color:** `Boolean`
Whether or not to enable colored output on TTYs. Note that ANSI
color sequences will not be written to a log file, only a real
terminal, so this option only applies if the log is being written
to a standard output which is connected to a terminal.
- **maxCache:** `Integer`
The maximum size of the cache. Telodendria relies heavily on caching
for performance reasons. The cache grows as data is loaded from the
data directory. All cache is stored in memory. This option limits the
size of the memory cache. If you have a system with a lot of memory
to spare, you'll get better performance if this option is set higher.
Otherwise, this value should be lowered on systems that have a
minimal amount of memory available.
## Examples
A number of example configuration files are shipped with Telodendria's
source code. They can be found in the `contrib/` directory if you are
viewing the source code directly. Otherwise, if you installed
Telodendria from a package, it is possible that the example
configurations were placed in the default locations for such files on
your operating system.

View file

@ -48,4 +48,13 @@ binaries.
## From Source
**TODO:** Update this section before #19 is closed.
If you would like to build Telodendria from source, you can download
the latest release code from the
[Releases](/Telodendria/telodendria/releases) page. After extracting
the tarball, read
[Contributing &rightarrow; Developing &rightarrow; Building &amp; Running](../CONTRIBUTING#building-and-running)
for details on how to build Telodendria.
If all goes well, you will find the server binary in the `build/`
directory. If an error occured, and you didn't modify the code,
please open an issue.

View file

@ -1,57 +0,0 @@
.Dd $Mdocdate: April 29 2023 $
.Dt SEND-PATCH 1
.Os Telodendria Project
.Sh NAME
.Nm send-patch
.Nd Submit a patch file to the Telodendria Patches Matrix room
.Sh SYNOPSIS
.Nm
.Op patch
.Sh DESCRIPTION
.Nm
is a simple shell script for submitting patch files to Telodendria's patch
room for review.
.Pp
.Nm
takes a single argument, a patch file. It also reads a number of environment
variables, as described in the following section. This script is designed to be
simple; it only pushes files into a hard-coded Matrix room. Thus, as far as
Matrix clients go, this one is a rather minimal one, and that is by design.
.Pp
This script exists so that users who are working on a machine that doesn't have
a Matrix client installed can still submit work to the Telodendria project. The
goal is to make development as accessible as possible.
.Pp
This script only supports password login, so if your homeserver does not
support password login, it will not work.
.Sh ENVIRONMENT
.Pp
.Nm
utilizes the following environment variables:
.Bl -tag -width Ds
.It Ev MXID
Your Matrix ID in the standard format. This is used to connect to your
homeserver to send the message.
.It Ev MXPW
Your Matrix account's password. If not set, you will be prompted for your
password by the script, unless
.Ev ACCESS_TOKEN
is set.
.It Ev ACCESS_TOKEN
If you already have an access token for your account, such as one from an
existing session, then you can set this environment variable to bypass the
password authentication flow.
.El
.Sh FILES
.Pp
.Nm
does utilize the
.Pa .env
file, just like
.Xr td 1 .
Consult that page for the specifics of the
.Pa .env
file.
.Sh SEE ALSO
.Xr td 1

View file

@ -1,63 +0,0 @@
.Dd $Mdocdate: April 29 2023 $
.Dt TP 1
.Os Telodendria Project
.Sh NAME
.Nm tp
.Nd Manage the official patch queue.
.Sh SYNOPSIS
.Np
.Op action
.Op patch
.Sh DESCRIPTION
.Pp
This script is probably not going to be very useful for anyone other
than the official project managers, but for completeness, this page
documents it.
.Pp
.Nm
is a simple script that is used to manage the patch queue. It offers a
simple way to fetch patches from the patch Matrix room and queue them
in a patch directory, which is then updated as patches are handled.
Contributions to Telodendria are entirely patch-based, so this script
makes dealing with patch files much more convenient.
.Pp
.Nm
doesn't implement a complex command line interface. It simply takes an
action as the first argument, and a patch file ID as the second
argument to some actions. The actions are as follows:
.Bl -tag -width Ds
.It ingress
Download all new patches from the patches room. This action is intended
to be called periodically from
.Xr cron 8
to fetch new patches.
.It queue
List all the patches in the queue, printing the first three lines of
each one so they can be easily identified.
.It view
View the specified patch. Note that the specified patch must be in
the queue. Once it is applied or rejected, this script offers no
facility for viewing it.
.It apply
Apply the specified patch to the current working directory.
.It reverse
Reverse the specified patch on the current working directory.
.It accept|reject
Accept or reject the specified patch by moving it to the appropriate
directory. These actions also prompt for a message, into which it is
expected that an explanation for why the patch was accepted or rejected
will be placed.
.El
.Sh ENVIRONMENT
.Pp
The following environment variables are read by the
.Nm
script:
.Bl -tag -width Ds
.It Ev TELODENDRIA_PUB
The base directory inside which the patch directory relies.
.It Ev HOMESERVER
The Matrix homeserver to contact for receiving patches.
.It Ev ACCESS_TOKEN
The access token to use to authenticate with the Matrix homeserver.
.El

View file

@ -1,218 +0,0 @@
.Dd $Mdocdate: April 20 2023 $
.Dt TELODENDRIA-CONFIG 7
.Os Telodendria Project
.Sh NAME
.Nm telodendria-config
.Nd Telodendria configuration.
.Sh DESCRIPTION
.Pp
Telodendria is designed to be configurable. It is configured using
JSON, which is intended to be submitted via the administrator API.
This page documents Telodendria's configuration JSON format, which
is used both in the administrator API, and on the disk in the
database. The configuration file on the disk in the database is
.Pa config.json ,
though that file should not be edited directly. Use the API described
in
.Xr telodendria-admin 7
instead.
.Sh DIRECTIVES
Here are the top-level directives:
.Bl -tag -width Ds
.It Ic listen Ar listenArr
An array of listener description objects. Telodendria supports
listening on multiple ports, and each port is configured
independently of the others. A listener description object looks
like this:
.Bl -tag -width Ds
.It Ic port Ar port
The port to listen on. Telodendria will bind to all interfaces, so it
is recommended to configure your firewall so you can control what is
allowed to access the Telodendria ports. Note that
Telodendria offers all APIs over each port; there is no way to
control which APIs are available over which ports, although all
APIs should be safe against attacks, so this should not be a
major concern.
.Pp
.Ar port
should be a decimal port number. This directive is required. Common
port numbers are 8008 for non-TLS, and 8448 for TLS.
.It Ic tls Ar tlsObj|null|false
Telodendria can be compiled with TLS support. If it is, then a
particular listener can be set to use TLS for connections. If
.Ic tls
is not
.Ar null
or
.Ar false ,
then it can be an object with the following directives:
.Bl -tag -width Ds
.It Ic cert Ar file
A certificate file in the format native to the platform's TLS
library. This can be an absolute path, otherwise it is relative
to the data directory.
.It Ic key Ar file
A key file in the format native to the platform's TLS library.
It follows the same rules as the certificate file.
.El
.El
.It Ic serverName Ar name
Configure the domain name of your homeserver. Note that Matrix servers
cannot be migrated to other domains, so once this is set, it should never
change unless you want unexpected things to happen, or you want to start
over.
.Ar name
should be a DNS name that can be publically resolved. This directive
is required.
.It Ic baseUrl Ar url
Set the server's base URL.
.Ar url
should be a valid URL, complete with the protocol. It does not need to
be the same as the server name; in fact, it is common for a subdomain of
the server name to be the base URL for the Matrix homeserver.
.Pp
This URL is the URL at which Matrix clients will connect to the server,
and is thus served as a part of the
.Pa .well-known
manifest.
.Pp
This directive is optional. If it is not specified, it is automatically
deduced from the server name.
.It Ic identityServer Ar url
The identity server that clients should use to perform identity lookups.
.Pp
.Ar url
follows the same rules as
.Ic baseUrl .
.Pp
This directive is optional. If it is not specified, it is automatically
set to be the same as the base URL.
.It Ic runAs Ar uidObj
The effective UNIX user and group to drop to after binding to the socket
and changing the filesystem root for the process. This only works if
Telodendria is running as the root user, and is used as a security mechanism.
If this option is set and Telodendria is started as a non-priviledged user,
then a warning is printed to the log if that user does not match what's
specified here. This directive is optional, but should be used as a sanity
check, if nothing more, to make sure the permissions are working properly.
.Pp
This directive takes an object with the following directives:
.Bl -tag -width Ds
.It Ic uid Ar user
The UNIX username to drop to. If
.Ic runAs
is specified, this directive is required.
.It Ic gid Ar group
The UNIX group to drop to. This directive is optional; if it is not
specified, then the value of
.Ic uid
is copied.
.El
.Ic log
directive is configured to write to a file, the log file will be written
in the data directory.
.Ar directory
should be an absolute path, under which all Telodendria data will live.
.It Ic federation Ar true|false
Whether to enable federation with other Matrix homeservers or not. Matrix is
by its very nature a federated protocol, but if you just want to run your
own internal chat server with no contact with the outside, then you can use
this option to disable federation. It is highly recommended to set this to
.Ar true ,
however, if you wish to be able to communicate with users on other Matrix
servers. This directive is required.
.It Ic registration Ar true|false
Whether or not to enable new user registration or not. For security and anti-spam
reasons, you can set this to
.Ar false .
If you do, you can still add users via the administrator API. In an ideal world,
everyone would run their own homeserver, so no public registration would ever
be required. Unfortunately, not everyone has the means to run their own homeserver,
especially because of the fact that public IPv4 addresses are becoming increasingly
harder to come by. If you would like to provide a service to those that are unable
to run their own homeserver, you can set this to
.Ar true ,
which will allow anyone to create an account. Telodendria should be capable of handling
a large amount of users without difficulty or security issues. This directive is
required.
.It Ic log Ar logObj
The log file configuration. Telodendria uses its own logging facility, which can
output logs to standard output, a file, or the syslog. This directive is required,
and it takes an object with the following directives:
.Bl -tag -width Ds
.It Ic output Ar stdout|file|syslog
The lot output destination. If set to
.Ar file ,
Telodendria will log to
.Pa telodendria.log
inside the data directory.
.It Ic level Ar error|warning|notice|message|debug
The level of messages to log at. Each level shows all the levels above it. For
example, setting the level to
.Ar error
will show only errors, while setting the level to
.Ar warning
will show warnings and errors.
.Ar notice
shows notices, warnings, and errors, and so on. The
.Ar debug
level shows all messages.
.It Ic timestampFormat Ar format|none|default
If you want to customize the timestamp format shown in the log, or disable it
altogether, you can do so via this option. Acceptable values are
.Ar none ,
.Ar default ,
or a formatter string as described by your system's
.Xr strftime 3 .
This option only applies if
.Ic log
is "stdout" or "file".
.It Ic color Ar true|false
Whether or not to enable colored output on TTYs. Note that ANSI color sequences
will not be written to a log file, only a real terminal, so this option only
applies if the log is being written to a standard output which is connected to
a terminal.
.Pp
This option only applies if
.Ic log
is "stdout".
.El
.It Ic threads Ar count
How many worker threads to spin up to handle requests. This should generally be
less than the total CPU core count, to prevent overloading the system. The most
efficient number of threads ultimately depends on the configuration of the
machine running Telodendria, so you may just have to play around with different
values here to see which gives the best performance.
.It Ic maxConnections Ar count
The maximum number of simultanious connections to allow to the daemon. This option
prevents the daemon from allocating large amounts of memory in the event that it
undergoes a denial of service attack. It typically does not need to be adjusted.
.It Ic maxCache Ar bytes
The maximum size of the cache. Telodendria relies heavily on caching to speed
things up. The cache grows as data is loaded from the data directory. All cache
is stored in memory. This option limits the size of the memory cache. If you have
a system that has a lot of memory, you'll get better performance if this option
is set higher. Otherwise, this value should be lowered on systems that have
minimal memory available.
.El
.Sh FILES
.Bl -tag -width Ds
.It Pa config.json
The configuration file stored on the filesystem in the data
directory. It is not recommended to edit this directly.
.It Pa /var/telodendria
The recommended data directory.
.El
.Sh EXAMPLES
.Pp
A number of example configuration files are shipped with
Telodendria's source code. They can be found in the
.Pa contrib/
directory if you are viewing the source code directly. Otherwise,
if you installed Telodendria as a package, it is possible that the
example configurations were placed in the default locations for
such files on your operating system.
.Sh SEE ALSO
.Xr telodendria-setup 7 ,
.Xr telodendria-admin 7
.Xr telodendria 8

View file

@ -1,268 +0,0 @@
.Dd $Mdocdate: March 10 2023 $
.Dt TELODENDRIA-CONTRIBUTING 7
.Os Telodendria Project
.Sh NAME
.Nm telodendria-contributing
.Nd Guide to contributing to the Telodendria project.
.Sh DESCRIPTION
Telodendria is an open source project. As such, it welcomes
contributions. There are many ways you can contribute, and any
way you can is greatly appreciated. This page contains some of
the ways you can help out.
.Sh REPORTING ISSUES
Please reach out to the Matrix rooms mentioned at the top of
.Xr telodendria 7 .
All issue tracking takes place in those rooms. Start by reaching
out to the general room, and if you think there's a legitimate
problem with Telodendria itself, then stick the issue in the
issues room, where it can be discussed further. Issues usually
remain in the Matrix rooms, but severe enough issues may be put
in a
.Pa TODO
file in the
.Xr cvs 1
repository so that they don't get lost.
.Sh DEVELOPING
The primary language used to write Telodendria code is ANSI C.
Other languages you'll find in the Telodendria repository include
shell scripts,
.Xr mdoc 7 ,
and a little bit of HTML and CSS. If you have any experience with
any of these languages, your contributions are valuable! Please follow
the guidelines on this page to ensure the contribution workflow goes
as smoothly as possible.
.Ss Getting the Code
If you'd like to hack on Telodendria, you'll need the following tools
in addition to a C compiler and POSIX shell:
.Bl -tag
.It Xr cvs 1
For checking out and updating your local copy of the source code.
.It Xr indent 1
For formatting your code before generating patches.
.It Xr patch 1
For applying patches to your local copy of the source code.
.El
.sp
All of these tools are built into OpenBSD. While you don't have to
use OpenBSD to develop Telodendria, it may make the process a bit
easier. In fact, these tools where chosen precisely because they
were built into my operating system of choice.
.sp
You can download an official release tarball from the website if
you would really like, but the preferred way to get the source
code for development is to check it out from CVS. This makes generating
patches a lot easier.
.Bd -literal -offset indent
$ cvs -d anoncvs@bancino.net:/cvs checkout -P Telodendria
$ cd Telodendria
.Ed
.sp
If you already checked out the code previously, make sure you update
your local copy before you start developing:
.Bd -literal -offset indent
$ cvs -q update -dP
.Ed
.sp
You should now have the latest source code. Follow the
.Sx CODE STYLE
as you make your changes. If the
.Xr cvs 1
command fails with a "Connection refused" error message, try setting
the
.Ev CVS_RSH
environment variable to "ssh", like this:
.Bd -literal -offset indent
$ export CVS_RSH=ssh
.Ed
.sp
Then run the checkout command again. Some versions of CVS on some
systems don't use SSH to checkout by default, so if yours doesn't,
you might want to put the above line into your shell init script.
.Ss Submitting Patches
Telodendria aims at remaining as minimal as possible. This doesn't just
mean minimal code, it also means a minimal development process, which is
why Telodendria doesn't use GitHub, GitLab, or even SourceHut. Instead,
the contribution workflow operates on submitting patch files to a public
Matrix room, sort of like the OpenBSD project operates on patch files
sent to a public mailing list.
.sp
If you're not used to manually creating and submitting patches instead of
just opening a "pull request," you should be pleased to hear that submitting
patches is fairly easy to do if you've got the CVS sources checked out. In
fact, I find it easier than having to make a GitHub account, forking a
project's repository, and then making a pull request for it. Once you have
made your changes in your local copy of the code, and you've configured your
environment properly as noted in the manual for
.Xr td 1 ,
just run the patch recipe:
.Bd -literal -offset indent
$ td patch
.Ed
.sp
This will automatically generate a patch file for all your changes, and then
open it in your preferred editor. You can also generate a patch file for only
certain files and directories. To do that, set
.Ev PATCHSET ,
like this:
.Bd -literal -offset indent
# Only write a patch for README.txt and the files in docs/
$ PATCHSET="README.txt docs/" td patch
.Ed
.sp
As you'll notice, the top of the patch file should have some email-style
headers that look like this:
.Bd -literal -offset indent
From: Jordan Bancino <@jordan:bancino.net>
Date: Fri Jul 29 03:21:21 PM EDT 2022
Subject: Document Patch Procedure
.Ed
.sp
As much information should be filled out for you, such as the date. An
attempt to fill out the From header was also made by
.Xr td 1 ,
but the information there can be modifed as necessary. Consult the manual
for
.Xr td 1
for more details. The Subject should very briefly describe what the patch
is about.
.sp
You'll also notice these lines in the patch:
.Bd -literal -offset indent
[ ] I have read the Telodendria Project development certificate of
origin, and certify that I have permission to submit this patch
under the conditions specified in it.
.Ed
.sp
This is a checkbox that tells me whether or not you actually have the
rights to submit your patch, and that once you submit your patch, your
code is bound by the Telodendria project license, which you can and
should view in
.Xr telodendria 7 .
The full text of the developer certificate of origin is as follows:
.Bl -enum
.It
The contribution was created in whole or in part by me, and I have the right
to submit it under the open source licenses of the Telodendria project; or
.It
The contribution is based upon a previous work that, to the best of my knowledge,
is covered under an appropriate open source license and I have the right under
that license to submit that work with modifications, whether created in whole
or in part by me, under the Telodendria project license; or
.It
The contribution whas provided directly to me by some other person who certified
(1), (2), or (3), and I have not modifed it.
.It
I understand and agree that this project and the contribution are made public
and that a record of the contribution\(emincluding all personal information
I submit with it\(emis maintained indefinitely and may be redistributed
consistent with this project or the open source licenses involved.
.El
.sp
If you agree to the above, fill in the square brackets with an 'x', and then after
the headers, but before the checkbox, write a more thorough description of the
patch and why it was created. Then, send the resulting patch file to the public
Matrix room using
.Xr send-patch 1 .
.sp
Try to keep your patches on topic\(emmake one patch file per feature or bug fix
being implemented. It is okay if your patches depend on previous patches, just
indicate that in the patch description. Note that it may take a while for
patches to be committed, and some patches may not be comitted at all. In either
case, all sent patches are queued from the Matrix room into the public patch
directory, so they can be referenced easier in the future. If you want to
reference a submitted patch in a Matrix message, email, or other digital medium,
it might be a good idea to link to it in the public patch directory.
.sp
The public patch directory works as follows: when you send your patch to the
Matrix room, it is downloaded by Telodendria Bot and placed in the
.Pa ingress/
directory, named as the message ID. Then, it is assigned a patch ID and
copied to the
.Pa p/
directory as just "%d.patch", where "%d" is obviously the patch ID. This is
a permanent link that will always reference your patch. Then, your patch will
be symlinked into the
.Pa queue/
directory. I have a script that automatically ingresses patches and queues them
for me, and I use this to review patches. If your patch is accepted, the queue
symlink will be moved to
.Pa accepted/
and the submitted patch will be committed to the official CVS repository.
If your patch is rejected for some reason, its symlink will be moved to the
.Pa rejected/
directory. Regardless of the state of your patch, it will always remain
permalinked in the
.Pa p/
directory, and when it is accepted or rejected, Telodendria Bot will send a
message to the Matrix room.
.sp
You're always welcome to inquire about rejected patches, and request that they
be reviewed again, or you can use them as a starting point for future patches.
.sp
The public patch directory is located at
.Lk https://telodendria.io/patches/
.Sh CODE STYLE
In general, these are the conventions used by the code base. This guide
may be slightly outdated or subject to change, but it should be a good
start. The source code itself is always the absolute source of truth, so
as long as you make your code look like the code surrounding it, you should
be fine.
.Bl -bullet
.It
All function, enumeration, structure, and header names are CamelCase. This
is preferred to snake_case because it is more compact.
.It
All variable names are lowerCamelCase. This is preferred to snake_case
because it is more compact.
.It
enumerations and structures are always typedef-ed to their same name. The
typedef should occur in the public API header, and the actual declaration
should live in the implementation file.
.It
A feature of the code base lives in a single C source file that has a
matching header. The header file should only export public symbols;
everything else in the C source should be static.
.It
Except where absolutely necessary, global variables are forbidden to
prevent problems with threads and whatnot. Every variable a function
needs should be passed to it either through a structure, or as a
separate argument.
.It
Anywhere curly braces are optional, there still must be curly braces. This
makes it easier to add on to the code later, and just makes things a bit
less ambiguous.
.El
.sp
As far as actually formatting the code goes, such as where to put brackets,
and whether or not to use tabs or spaces, use
.Xr indent 1
to take care of all of that. The root of the CVS repository has a
.Pa .indent.pro
that should automatically be loaded by
.Xr indent 1
to set the correct rules. If you don't have a working
.Xr indent 1 ,
then just indicate in your patch that I should run my
.Xr indent 1
on the code after applying it. Although in reality, I'll likely
run my own
.Xr indent 1
on the code anyway, just to make sure the spacing is consistent, if nothing
else.
.Pp
This project places a strong emphasis on documentation. Well-documented
code is fundamental to a successful project, so when you are writing code,
please also make sure it is documented appropriately.
.Bl -bullet
.It
If you are adding a header, make sure you add a man page that documents
all the functions in the header.
.It
If you're adding a function, make sure you add documentation for it
to the appropriate man page for the header that your function resides
in. Do note that you do not have to document static functions, only
public API functions.
.El
.Pp
If your patch does not also include proper documentation, it will
likely be rejected.

View file

@ -71,6 +71,7 @@ RouterBuild(void)
R("/_matrix/client/v3/profile/(.*)", RouteUserProfile);
R("/_matrix/client/v3/profile/(.*)/(avatar_url|displayname)", RouteUserProfile);
R("/_matrix/client/v3/user_directory/search", RouteUserDirectory);
R("/_matrix/client/v3/user/(.*)/filter", RouteFilter);
R("/_matrix/client/v3/user/(.*)/filter/(.*)", RouteFilter);

View file

@ -0,0 +1,213 @@
/*
* Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <Routes.h>
#include <Array.h>
#include <HashMap.h>
#include <Json.h>
#include <Str.h>
#include <Memory.h>
#include <User.h>
#include <Db.h>
ROUTE_IMPL(RouteUserDirectory, path, argp)
{
RouteArgs *args = argp;
HashMap *response = NULL;
HashMap *request = NULL;
Array *users = NULL;
Array *results = NULL;
Db *db = args->matrixArgs->db;
Config *config = NULL;
User *user = NULL;
JsonValue *val = NULL;
char *token = NULL;
char *searchTerm = NULL;
char *requesterName = NULL;
char *error;
size_t limit = 10;
size_t i, included;
(void) path;
if (HttpRequestMethodGet(args->context) != HTTP_POST)
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_UNRECOGNIZED, NULL);
goto finish;
}
request = JsonDecode(HttpServerStream(args->context));
if (!request)
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_NOT_JSON, NULL);
goto finish;
}
response = MatrixGetAccessToken(args->context, &token);
if (response)
{
return response;
}
/* TODO: Actually use information related to the user. */
user = UserAuthenticate(db, token);
if (!user)
{
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
response = MatrixErrorCreate(M_UNKNOWN_TOKEN, NULL);
goto finish;
}
requesterName = UserGetName(user);
/* Parse limit and search_term */
if (!(val = JsonGet(request, 1, "search_term")) ||
JsonValueType(val) != JSON_STRING)
{
/* The Spec requires search_term to be set to a string. */
HttpResponseStatus(args->context, HTTP_BAD_REQUEST);
error = "search_term is not a string, or is non-existent";
response = MatrixErrorCreate(M_BAD_JSON, error);
goto finish;
}
searchTerm = StrLower(JsonValueAsString(val));
if ((val = JsonGet(request, 1, "limit")) &&
JsonValueType(val) == JSON_INTEGER)
{
/* It is however far more leinent on limit, with 10 by default. */
limit = JsonValueAsInteger(val);
}
response = HashMapCreate();
results = ArrayCreate();
/* TODO: Check for users matching search term and users outside our
* local server. */
users = DbList(db, 1, "users");
/* Offending line? */
config = ConfigLock(db);
if (!config)
{
error = "Directory endpoint failed to lock configuration.";
Log(LOG_ERR, error);
HttpResponseStatus(args->context, HTTP_INTERNAL_SERVER_ERROR);
response = MatrixErrorCreate(M_UNKNOWN, error);
goto finish;
}
for (i = 0, included = 0; i < ArraySize(users) && included < limit; i++)
{
HashMap *obj;
User *currentUser;
char *name = ArrayGet(users, i);
char *displayName;
char *lowerDisplayName;
char *avatarUrl;
if (!StrEquals(name, requesterName))
{
currentUser = UserLock(db, name);
}
else
{
currentUser = user;
}
displayName = UserGetProfile(currentUser, "displayname");
lowerDisplayName = StrLower(displayName);
avatarUrl = UserGetProfile(currentUser, "avatar_url");
/* Check for the user ID and display name. */
if (strstr(name, searchTerm) ||
(lowerDisplayName && strstr(lowerDisplayName, searchTerm)))
{
included++;
obj = HashMapCreate();
if (displayName)
{
JsonSet(obj, JsonValueString(displayName), 1, "display_name");
}
if (avatarUrl)
{
JsonSet(obj, JsonValueString(displayName), 1, "avatar_url");
}
if (name)
{
char *userID = StrConcat(4, "@", name, ":", config->serverName);
JsonSet(obj, JsonValueString(userID), 1, "user_id");
Free(userID);
}
ArrayAdd(results, JsonValueObject(obj));
}
if (lowerDisplayName)
{
Free(lowerDisplayName);
}
if (!StrEquals(name, requesterName))
{
UserUnlock(currentUser);
}
}
JsonSet(response, JsonValueArray(results), 1, "results");
JsonSet(response, JsonValueBoolean(included == limit), 1, "limited");
finish:
if (user)
{
UserUnlock(user);
}
if (request)
{
JsonFree(request);
}
if (searchTerm)
{
Free(searchTerm);
}
if (users)
{
DbListFree(users);
}
if (config)
{
ConfigUnlock(config);
}
return response;
}

View file

@ -172,7 +172,10 @@ ROUTE_IMPL(RouteUserProfile, path, argp)
}
finish:
ConfigUnlock(config);
Free(username);
/* Username is handled by the router, freeing it *will* cause issues
* (see #33). I honestly don't know how it didn't come to bite us sooner.
Free(username); */
Free(entry);
UserIdFree(userId);
UserUnlock(user);

299
src/Schema/ClientEvent.c Normal file
View file

@ -0,0 +1,299 @@
/* Generated by j2s */
#include <Schema/ClientEvent.h>
#include <Memory.h>
#include <Json.h>
#include <Str.h>
int
ClientEventUnsignedDataFromJson(HashMap *json, ClientEventUnsignedData *out, char **errp)
{
JsonValue *val;
int enumParseRes;
(void) enumParseRes;
if (!json | !out)
{
*errp = "Invalid pointers passed to ClientEventUnsignedDataFromJson()";
return 0;
}
val = HashMapGet(json, "redacted_because");
if (val)
{
if (JsonValueType(val) != JSON_OBJECT)
{
*errp = "ClientEventUnsignedData.redacted_because must be of type object.";
return 0;
}
out->redacted_because = JsonValueAsObject(JsonValueDuplicate(val));
}
val = HashMapGet(json, "transaction_id");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
*errp = "ClientEventUnsignedData.transaction_id must be of type string.";
return 0;
}
out->transaction_id = StrDuplicate(JsonValueAsString(val));
}
val = HashMapGet(json, "prev_content");
if (val)
{
if (JsonValueType(val) != JSON_OBJECT)
{
*errp = "ClientEventUnsignedData.prev_content must be of type object.";
return 0;
}
out->prev_content = JsonValueAsObject(JsonValueDuplicate(val));
}
val = HashMapGet(json, "age");
if (val)
{
if (JsonValueType(val) != JSON_INTEGER)
{
*errp = "ClientEventUnsignedData.age must be of type integer.";
return 0;
}
out->age = JsonValueAsInteger(val);
}
return 1;
}
HashMap *
ClientEventUnsignedDataToJson(ClientEventUnsignedData *val)
{
HashMap *json;
if (!val)
{
return NULL;
}
json = HashMapCreate();
if (!json)
{
return NULL;
}
HashMapSet(json, "redacted_because", JsonValueObject(JsonDuplicate(val->redacted_because)));
HashMapSet(json, "transaction_id", JsonValueString(val->transaction_id));
HashMapSet(json, "prev_content", JsonValueObject(JsonDuplicate(val->prev_content)));
HashMapSet(json, "age", JsonValueInteger(val->age));
return json;
}
void
ClientEventUnsignedDataFree(ClientEventUnsignedData *val)
{
if (!val)
{
return;
}
JsonFree(val->redacted_because);
Free(val->transaction_id);
JsonFree(val->prev_content);
}
int
ClientEventFromJson(HashMap *json, ClientEvent *out, char **errp)
{
JsonValue *val;
int enumParseRes;
(void) enumParseRes;
if (!json | !out)
{
*errp = "Invalid pointers passed to ClientEventFromJson()";
return 0;
}
val = HashMapGet(json, "origin_server_ts");
if (val)
{
if (JsonValueType(val) != JSON_INTEGER)
{
*errp = "ClientEvent.origin_server_ts must be of type integer.";
return 0;
}
out->origin_server_ts = JsonValueAsInteger(val);
}
else
{
*errp = "ClientEvent.origin_server_ts is required.";
return 0;
}
val = HashMapGet(json, "content");
if (val)
{
if (JsonValueType(val) != JSON_OBJECT)
{
*errp = "ClientEvent.content must be of type object.";
return 0;
}
out->content = JsonValueAsObject(JsonValueDuplicate(val));
}
else
{
*errp = "ClientEvent.content is required.";
return 0;
}
val = HashMapGet(json, "room_id");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
*errp = "ClientEvent.room_id must be of type string.";
return 0;
}
out->room_id = StrDuplicate(JsonValueAsString(val));
}
else
{
*errp = "ClientEvent.room_id is required.";
return 0;
}
val = HashMapGet(json, "sender");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
*errp = "ClientEvent.sender must be of type string.";
return 0;
}
out->sender = StrDuplicate(JsonValueAsString(val));
}
else
{
*errp = "ClientEvent.sender is required.";
return 0;
}
val = HashMapGet(json, "state_key");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
*errp = "ClientEvent.state_key must be of type string.";
return 0;
}
out->state_key = StrDuplicate(JsonValueAsString(val));
}
val = HashMapGet(json, "event_id");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
*errp = "ClientEvent.event_id must be of type string.";
return 0;
}
out->event_id = StrDuplicate(JsonValueAsString(val));
}
else
{
*errp = "ClientEvent.event_id is required.";
return 0;
}
val = HashMapGet(json, "type");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
*errp = "ClientEvent.type must be of type string.";
return 0;
}
out->type = StrDuplicate(JsonValueAsString(val));
}
else
{
*errp = "ClientEvent.type is required.";
return 0;
}
val = HashMapGet(json, "unsigned");
if (val)
{
if (JsonValueType(val) != JSON_OBJECT)
{
*errp = "ClientEvent.unsigned must be of type ClientEventUnsignedData.";
return 0;
}
if (!ClientEventUnsignedDataFromJson(JsonValueAsObject(val), &out->_unsigned, errp))
{
return 0;
}
}
return 1;
}
HashMap *
ClientEventToJson(ClientEvent *val)
{
HashMap *json;
if (!val)
{
return NULL;
}
json = HashMapCreate();
if (!json)
{
return NULL;
}
HashMapSet(json, "origin_server_ts", JsonValueInteger(val->origin_server_ts));
HashMapSet(json, "content", JsonValueObject(JsonDuplicate(val->content)));
HashMapSet(json, "room_id", JsonValueString(val->room_id));
HashMapSet(json, "sender", JsonValueString(val->sender));
HashMapSet(json, "state_key", JsonValueString(val->state_key));
HashMapSet(json, "event_id", JsonValueString(val->event_id));
HashMapSet(json, "type", JsonValueString(val->type));
HashMapSet(json, "unsigned", JsonValueObject(ClientEventUnsignedDataToJson(&val->_unsigned)));
return json;
}
void
ClientEventFree(ClientEvent *val)
{
if (!val)
{
return;
}
JsonFree(val->content);
Free(val->room_id);
Free(val->sender);
Free(val->state_key);
Free(val->event_id);
Free(val->type);
ClientEventUnsignedDataFree(&val->_unsigned);
}

1193
src/Schema/Filter.c Normal file

File diff suppressed because it is too large Load diff

477
src/Schema/PduV1.c Normal file
View file

@ -0,0 +1,477 @@
/* Generated by j2s */
#include <Schema/PduV1.h>
#include <Memory.h>
#include <Json.h>
#include <Str.h>
int
PduV1EventHashFromJson(HashMap *json, PduV1EventHash *out, char **errp)
{
JsonValue *val;
int enumParseRes;
(void) enumParseRes;
if (!json | !out)
{
*errp = "Invalid pointers passed to PduV1EventHashFromJson()";
return 0;
}
val = HashMapGet(json, "sha256");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
*errp = "PduV1EventHash.sha256 must be of type string.";
return 0;
}
out->sha256 = StrDuplicate(JsonValueAsString(val));
}
else
{
*errp = "PduV1EventHash.sha256 is required.";
return 0;
}
return 1;
}
HashMap *
PduV1EventHashToJson(PduV1EventHash *val)
{
HashMap *json;
if (!val)
{
return NULL;
}
json = HashMapCreate();
if (!json)
{
return NULL;
}
HashMapSet(json, "sha256", JsonValueString(val->sha256));
return json;
}
void
PduV1EventHashFree(PduV1EventHash *val)
{
if (!val)
{
return;
}
Free(val->sha256);
}
int
PduV1FromJson(HashMap *json, PduV1 *out, char **errp)
{
JsonValue *val;
int enumParseRes;
(void) enumParseRes;
if (!json | !out)
{
*errp = "Invalid pointers passed to PduV1FromJson()";
return 0;
}
val = HashMapGet(json, "origin_server_ts");
if (val)
{
if (JsonValueType(val) != JSON_INTEGER)
{
*errp = "PduV1.origin_server_ts must be of type integer.";
return 0;
}
out->origin_server_ts = JsonValueAsInteger(val);
}
else
{
*errp = "PduV1.origin_server_ts is required.";
return 0;
}
val = HashMapGet(json, "content");
if (val)
{
if (JsonValueType(val) != JSON_OBJECT)
{
*errp = "PduV1.content must be of type object.";
return 0;
}
out->content = JsonValueAsObject(JsonValueDuplicate(val));
}
else
{
*errp = "PduV1.content is required.";
return 0;
}
val = HashMapGet(json, "redacts");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
*errp = "PduV1.redacts must be of type string.";
return 0;
}
out->redacts = StrDuplicate(JsonValueAsString(val));
}
val = HashMapGet(json, "sender");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
*errp = "PduV1.sender must be of type string.";
return 0;
}
out->sender = StrDuplicate(JsonValueAsString(val));
}
else
{
*errp = "PduV1.sender is required.";
return 0;
}
val = HashMapGet(json, "depth");
if (val)
{
if (JsonValueType(val) != JSON_INTEGER)
{
*errp = "PduV1.depth must be of type integer.";
return 0;
}
out->depth = JsonValueAsInteger(val);
}
else
{
*errp = "PduV1.depth is required.";
return 0;
}
val = HashMapGet(json, "prev_events");
if (val)
{
if (JsonValueType(val) != JSON_ARRAY)
{
*errp = "PduV1.prev_events must be of type array.";
return 0;
}
out->prev_events = JsonValueAsArray(JsonValueDuplicate(val));
}
else
{
*errp = "PduV1.prev_events is required.";
return 0;
}
val = HashMapGet(json, "type");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
*errp = "PduV1.type must be of type string.";
return 0;
}
out->type = StrDuplicate(JsonValueAsString(val));
}
else
{
*errp = "PduV1.type is required.";
return 0;
}
val = HashMapGet(json, "unsigned");
if (val)
{
if (JsonValueType(val) != JSON_OBJECT)
{
*errp = "PduV1.unsigned must be of type PduV1UnsignedData.";
return 0;
}
if (!PduV1UnsignedDataFromJson(JsonValueAsObject(val), &out->_unsigned, errp))
{
return 0;
}
}
val = HashMapGet(json, "auth_events");
if (val)
{
if (JsonValueType(val) != JSON_ARRAY)
{
*errp = "PduV1.auth_events must be of type array.";
return 0;
}
out->auth_events = JsonValueAsArray(JsonValueDuplicate(val));
}
else
{
*errp = "PduV1.auth_events is required.";
return 0;
}
val = HashMapGet(json, "room_id");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
*errp = "PduV1.room_id must be of type string.";
return 0;
}
out->room_id = StrDuplicate(JsonValueAsString(val));
}
else
{
*errp = "PduV1.room_id is required.";
return 0;
}
val = HashMapGet(json, "state_key");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
*errp = "PduV1.state_key must be of type string.";
return 0;
}
out->state_key = StrDuplicate(JsonValueAsString(val));
}
val = HashMapGet(json, "signatures");
if (val)
{
if (JsonValueType(val) != JSON_OBJECT)
{
*errp = "PduV1.signatures must be of type object.";
return 0;
}
out->signatures = JsonValueAsObject(JsonValueDuplicate(val));
}
else
{
*errp = "PduV1.signatures is required.";
return 0;
}
val = HashMapGet(json, "event_id");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
*errp = "PduV1.event_id must be of type string.";
return 0;
}
out->event_id = StrDuplicate(JsonValueAsString(val));
}
else
{
*errp = "PduV1.event_id is required.";
return 0;
}
val = HashMapGet(json, "hashes");
if (val)
{
if (JsonValueType(val) != JSON_OBJECT)
{
*errp = "PduV1.hashes must be of type PduV1EventHash.";
return 0;
}
if (!PduV1EventHashFromJson(JsonValueAsObject(val), &out->hashes, errp))
{
return 0;
}
}
else
{
*errp = "PduV1.hashes is required.";
return 0;
}
return 1;
}
HashMap *
PduV1ToJson(PduV1 *val)
{
HashMap *json;
if (!val)
{
return NULL;
}
json = HashMapCreate();
if (!json)
{
return NULL;
}
HashMapSet(json, "origin_server_ts", JsonValueInteger(val->origin_server_ts));
HashMapSet(json, "content", JsonValueObject(JsonDuplicate(val->content)));
HashMapSet(json, "redacts", JsonValueString(val->redacts));
HashMapSet(json, "sender", JsonValueString(val->sender));
HashMapSet(json, "depth", JsonValueInteger(val->depth));
if (val->prev_events)
{
size_t i;
Array *jsonArr = ArrayCreate();
if (!jsonArr)
{
JsonFree(json);
return NULL;
}
for (i = 0; i < ArraySize(val->prev_events); i++)
{
ArrayAdd(jsonArr, JsonValueDuplicate(ArrayGet(val->prev_events, i)));
}
HashMapSet(json, "prev_events", JsonValueArray(jsonArr));
}
HashMapSet(json, "type", JsonValueString(val->type));
HashMapSet(json, "unsigned", JsonValueObject(PduV1UnsignedDataToJson(&val->_unsigned)));
if (val->auth_events)
{
size_t i;
Array *jsonArr = ArrayCreate();
if (!jsonArr)
{
JsonFree(json);
return NULL;
}
for (i = 0; i < ArraySize(val->auth_events); i++)
{
ArrayAdd(jsonArr, JsonValueDuplicate(ArrayGet(val->auth_events, i)));
}
HashMapSet(json, "auth_events", JsonValueArray(jsonArr));
}
HashMapSet(json, "room_id", JsonValueString(val->room_id));
HashMapSet(json, "state_key", JsonValueString(val->state_key));
HashMapSet(json, "signatures", JsonValueObject(JsonDuplicate(val->signatures)));
HashMapSet(json, "event_id", JsonValueString(val->event_id));
HashMapSet(json, "hashes", JsonValueObject(PduV1EventHashToJson(&val->hashes)));
return json;
}
void
PduV1Free(PduV1 *val)
{
if (!val)
{
return;
}
JsonFree(val->content);
Free(val->redacts);
Free(val->sender);
if (val->prev_events)
{
size_t i;
for (i = 0; i < ArraySize(val->prev_events); i++)
{
JsonValueFree(ArrayGet(val->prev_events, i));
}
ArrayFree(val->prev_events);
}
Free(val->type);
PduV1UnsignedDataFree(&val->_unsigned);
if (val->auth_events)
{
size_t i;
for (i = 0; i < ArraySize(val->auth_events); i++)
{
JsonValueFree(ArrayGet(val->auth_events, i));
}
ArrayFree(val->auth_events);
}
Free(val->room_id);
Free(val->state_key);
JsonFree(val->signatures);
Free(val->event_id);
PduV1EventHashFree(&val->hashes);
}
int
PduV1UnsignedDataFromJson(HashMap *json, PduV1UnsignedData *out, char **errp)
{
JsonValue *val;
int enumParseRes;
(void) enumParseRes;
if (!json | !out)
{
*errp = "Invalid pointers passed to PduV1UnsignedDataFromJson()";
return 0;
}
val = HashMapGet(json, "age");
if (val)
{
if (JsonValueType(val) != JSON_INTEGER)
{
*errp = "PduV1UnsignedData.age must be of type integer.";
return 0;
}
out->age = JsonValueAsInteger(val);
}
return 1;
}
HashMap *
PduV1UnsignedDataToJson(PduV1UnsignedData *val)
{
HashMap *json;
if (!val)
{
return NULL;
}
json = HashMapCreate();
if (!json)
{
return NULL;
}
HashMapSet(json, "age", JsonValueInteger(val->age));
return json;
}
void
PduV1UnsignedDataFree(PduV1UnsignedData *val)
{
if (!val)
{
return;
}
}

500
src/Schema/PduV3.c Normal file
View file

@ -0,0 +1,500 @@
/* Generated by j2s */
#include <Schema/PduV3.h>
#include <Memory.h>
#include <Json.h>
#include <Str.h>
int
PduV3EventHashFromJson(HashMap *json, PduV3EventHash *out, char **errp)
{
JsonValue *val;
int enumParseRes;
(void) enumParseRes;
if (!json | !out)
{
*errp = "Invalid pointers passed to PduV3EventHashFromJson()";
return 0;
}
val = HashMapGet(json, "sha256");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
*errp = "PduV3EventHash.sha256 must be of type string.";
return 0;
}
out->sha256 = StrDuplicate(JsonValueAsString(val));
}
else
{
*errp = "PduV3EventHash.sha256 is required.";
return 0;
}
return 1;
}
HashMap *
PduV3EventHashToJson(PduV3EventHash *val)
{
HashMap *json;
if (!val)
{
return NULL;
}
json = HashMapCreate();
if (!json)
{
return NULL;
}
HashMapSet(json, "sha256", JsonValueString(val->sha256));
return json;
}
void
PduV3EventHashFree(PduV3EventHash *val)
{
if (!val)
{
return;
}
Free(val->sha256);
}
int
PduV3FromJson(HashMap *json, PduV3 *out, char **errp)
{
JsonValue *val;
int enumParseRes;
(void) enumParseRes;
if (!json | !out)
{
*errp = "Invalid pointers passed to PduV3FromJson()";
return 0;
}
val = HashMapGet(json, "origin_server_ts");
if (val)
{
if (JsonValueType(val) != JSON_INTEGER)
{
*errp = "PduV3.origin_server_ts must be of type integer.";
return 0;
}
out->origin_server_ts = JsonValueAsInteger(val);
}
else
{
*errp = "PduV3.origin_server_ts is required.";
return 0;
}
val = HashMapGet(json, "content");
if (val)
{
if (JsonValueType(val) != JSON_OBJECT)
{
*errp = "PduV3.content must be of type object.";
return 0;
}
out->content = JsonValueAsObject(JsonValueDuplicate(val));
}
else
{
*errp = "PduV3.content is required.";
return 0;
}
val = HashMapGet(json, "redacts");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
*errp = "PduV3.redacts must be of type string.";
return 0;
}
out->redacts = StrDuplicate(JsonValueAsString(val));
}
val = HashMapGet(json, "sender");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
*errp = "PduV3.sender must be of type string.";
return 0;
}
out->sender = StrDuplicate(JsonValueAsString(val));
}
else
{
*errp = "PduV3.sender is required.";
return 0;
}
val = HashMapGet(json, "depth");
if (val)
{
if (JsonValueType(val) != JSON_INTEGER)
{
*errp = "PduV3.depth must be of type integer.";
return 0;
}
out->depth = JsonValueAsInteger(val);
}
else
{
*errp = "PduV3.depth is required.";
return 0;
}
val = HashMapGet(json, "prev_events");
if (val)
{
if (JsonValueType(val) != JSON_ARRAY)
{
*errp = "PduV3.prev_events must be of type [string].";
return 0;
}
out->prev_events = ArrayCreate();
if (!out->prev_events)
{
*errp = "Failed to allocate memory for PduV3.prev_events.";
return 0;
}
else
{
Array *arr = JsonValueAsArray(val);
size_t i;
for (i = 0; i <ArraySize(arr); i++)
{
JsonValue *v = ArrayGet(arr, i);
if (JsonValueType(v) != JSON_STRING)
{
*errp = "PduV3.prev_events[] contains an invalid value.";
return 0;
}
ArrayAdd(out->prev_events, StrDuplicate(JsonValueAsString(v)));
}
}
}
else
{
*errp = "PduV3.prev_events is required.";
return 0;
}
val = HashMapGet(json, "type");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
*errp = "PduV3.type must be of type string.";
return 0;
}
out->type = StrDuplicate(JsonValueAsString(val));
}
else
{
*errp = "PduV3.type is required.";
return 0;
}
val = HashMapGet(json, "unsigned");
if (val)
{
if (JsonValueType(val) != JSON_OBJECT)
{
*errp = "PduV3.unsigned must be of type PduV3UnsignedData.";
return 0;
}
if (!PduV3UnsignedDataFromJson(JsonValueAsObject(val), &out->_unsigned, errp))
{
return 0;
}
}
val = HashMapGet(json, "auth_events");
if (val)
{
if (JsonValueType(val) != JSON_ARRAY)
{
*errp = "PduV3.auth_events must be of type [string].";
return 0;
}
out->auth_events = ArrayCreate();
if (!out->auth_events)
{
*errp = "Failed to allocate memory for PduV3.auth_events.";
return 0;
}
else
{
Array *arr = JsonValueAsArray(val);
size_t i;
for (i = 0; i <ArraySize(arr); i++)
{
JsonValue *v = ArrayGet(arr, i);
if (JsonValueType(v) != JSON_STRING)
{
*errp = "PduV3.auth_events[] contains an invalid value.";
return 0;
}
ArrayAdd(out->auth_events, StrDuplicate(JsonValueAsString(v)));
}
}
}
else
{
*errp = "PduV3.auth_events is required.";
return 0;
}
val = HashMapGet(json, "room_id");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
*errp = "PduV3.room_id must be of type string.";
return 0;
}
out->room_id = StrDuplicate(JsonValueAsString(val));
}
else
{
*errp = "PduV3.room_id is required.";
return 0;
}
val = HashMapGet(json, "state_key");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
*errp = "PduV3.state_key must be of type string.";
return 0;
}
out->state_key = StrDuplicate(JsonValueAsString(val));
}
val = HashMapGet(json, "signatures");
if (val)
{
if (JsonValueType(val) != JSON_OBJECT)
{
*errp = "PduV3.signatures must be of type object.";
return 0;
}
out->signatures = JsonValueAsObject(JsonValueDuplicate(val));
}
else
{
*errp = "PduV3.signatures is required.";
return 0;
}
val = HashMapGet(json, "hashes");
if (val)
{
if (JsonValueType(val) != JSON_OBJECT)
{
*errp = "PduV3.hashes must be of type PduV3EventHash.";
return 0;
}
if (!PduV3EventHashFromJson(JsonValueAsObject(val), &out->hashes, errp))
{
return 0;
}
}
else
{
*errp = "PduV3.hashes is required.";
return 0;
}
return 1;
}
HashMap *
PduV3ToJson(PduV3 *val)
{
HashMap *json;
if (!val)
{
return NULL;
}
json = HashMapCreate();
if (!json)
{
return NULL;
}
HashMapSet(json, "origin_server_ts", JsonValueInteger(val->origin_server_ts));
HashMapSet(json, "content", JsonValueObject(JsonDuplicate(val->content)));
HashMapSet(json, "redacts", JsonValueString(val->redacts));
HashMapSet(json, "sender", JsonValueString(val->sender));
HashMapSet(json, "depth", JsonValueInteger(val->depth));
if (val->prev_events)
{
size_t i;
Array *jsonArr = ArrayCreate();
if (!jsonArr)
{
JsonFree(json);
return NULL;
}
for (i = 0; i < ArraySize(val->prev_events); i++)
{
ArrayAdd(jsonArr, JsonValueString(ArrayGet(val->prev_events, i)));
}
HashMapSet(json, "prev_events", JsonValueArray(jsonArr));
}
HashMapSet(json, "type", JsonValueString(val->type));
HashMapSet(json, "unsigned", JsonValueObject(PduV3UnsignedDataToJson(&val->_unsigned)));
if (val->auth_events)
{
size_t i;
Array *jsonArr = ArrayCreate();
if (!jsonArr)
{
JsonFree(json);
return NULL;
}
for (i = 0; i < ArraySize(val->auth_events); i++)
{
ArrayAdd(jsonArr, JsonValueString(ArrayGet(val->auth_events, i)));
}
HashMapSet(json, "auth_events", JsonValueArray(jsonArr));
}
HashMapSet(json, "room_id", JsonValueString(val->room_id));
HashMapSet(json, "state_key", JsonValueString(val->state_key));
HashMapSet(json, "signatures", JsonValueObject(JsonDuplicate(val->signatures)));
HashMapSet(json, "hashes", JsonValueObject(PduV3EventHashToJson(&val->hashes)));
return json;
}
void
PduV3Free(PduV3 *val)
{
if (!val)
{
return;
}
JsonFree(val->content);
Free(val->redacts);
Free(val->sender);
if (val->prev_events)
{
size_t i;
for (i = 0; i < ArraySize(val->prev_events); i++)
{
Free(ArrayGet(val->prev_events, i));
}
ArrayFree(val->prev_events);
}
Free(val->type);
PduV3UnsignedDataFree(&val->_unsigned);
if (val->auth_events)
{
size_t i;
for (i = 0; i < ArraySize(val->auth_events); i++)
{
Free(ArrayGet(val->auth_events, i));
}
ArrayFree(val->auth_events);
}
Free(val->room_id);
Free(val->state_key);
JsonFree(val->signatures);
PduV3EventHashFree(&val->hashes);
}
int
PduV3UnsignedDataFromJson(HashMap *json, PduV3UnsignedData *out, char **errp)
{
JsonValue *val;
int enumParseRes;
(void) enumParseRes;
if (!json | !out)
{
*errp = "Invalid pointers passed to PduV3UnsignedDataFromJson()";
return 0;
}
val = HashMapGet(json, "age");
if (val)
{
if (JsonValueType(val) != JSON_INTEGER)
{
*errp = "PduV3UnsignedData.age must be of type integer.";
return 0;
}
out->age = JsonValueAsInteger(val);
}
return 1;
}
HashMap *
PduV3UnsignedDataToJson(PduV3UnsignedData *val)
{
HashMap *json;
if (!val)
{
return NULL;
}
json = HashMapCreate();
if (!json)
{
return NULL;
}
HashMapSet(json, "age", JsonValueInteger(val->age));
return json;
}
void
PduV3UnsignedDataFree(PduV3UnsignedData *val)
{
if (!val)
{
return;
}
}

View file

@ -0,0 +1,712 @@
/* Generated by j2s */
#include <Schema/RoomCreateRequest.h>
#include <Memory.h>
#include <Json.h>
#include <Str.h>
int
RoomVisibilityFromStr(char *str)
{
if (StrEquals(str, "public"))
{
return ROOM_PUBLIC;
}
else if (StrEquals(str, "private"))
{
return ROOM_PRIVATE;
}
else
{
return -1;
}
}
char *
RoomVisibilityToStr(RoomVisibility val)
{
switch (val)
{
case ROOM_PUBLIC:
return "public";
case ROOM_PRIVATE:
return "private";
default:
return NULL;
}
}
int
RoomCreateRequestFromJson(HashMap *json, RoomCreateRequest *out, char **errp)
{
JsonValue *val;
int enumParseRes;
(void) enumParseRes;
if (!json | !out)
{
*errp = "Invalid pointers passed to RoomCreateRequestFromJson()";
return 0;
}
val = HashMapGet(json, "invite");
if (val)
{
if (JsonValueType(val) != JSON_ARRAY)
{
*errp = "RoomCreateRequest.invite must be of type [string].";
return 0;
}
out->invite = ArrayCreate();
if (!out->invite)
{
*errp = "Failed to allocate memory for RoomCreateRequest.invite.";
return 0;
}
else
{
Array *arr = JsonValueAsArray(val);
size_t i;
for (i = 0; i <ArraySize(arr); i++)
{
JsonValue *v = ArrayGet(arr, i);
if (JsonValueType(v) != JSON_STRING)
{
*errp = "RoomCreateRequest.invite[] contains an invalid value.";
return 0;
}
ArrayAdd(out->invite, StrDuplicate(JsonValueAsString(v)));
}
}
}
val = HashMapGet(json, "room_version");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
*errp = "RoomCreateRequest.room_version must be of type string.";
return 0;
}
out->room_version = StrDuplicate(JsonValueAsString(val));
}
val = HashMapGet(json, "invite_3pid");
if (val)
{
if (JsonValueType(val) != JSON_ARRAY)
{
*errp = "RoomCreateRequest.invite_3pid must be of type [RoomInvite3Pid].";
return 0;
}
out->invite_3pid = ArrayCreate();
if (!out->invite_3pid)
{
*errp = "Failed to allocate memory for RoomCreateRequest.invite_3pid.";
return 0;
}
else
{
Array *arr = JsonValueAsArray(val);
size_t i;
for (i = 0; i <ArraySize(arr); i++)
{
JsonValue *v = ArrayGet(arr, i);
RoomInvite3Pid *parsed;
if (JsonValueType(v) != JSON_OBJECT)
{
*errp = "RoomCreateRequest.invite_3pid[] contains an invalid value.";
return 0;
}
parsed = Malloc(sizeof(RoomInvite3Pid));
if (!parsed)
{
*errp = "Unable to allocate memory for array value.";
return 0;
}
if (!RoomInvite3PidFromJson(JsonValueAsObject(v), parsed, errp))
{
RoomInvite3PidFree(parsed);
Free(parsed);
return 0;
}
ArrayAdd(out->invite_3pid, parsed);
}
}
}
val = HashMapGet(json, "topic");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
*errp = "RoomCreateRequest.topic must be of type string.";
return 0;
}
out->topic = StrDuplicate(JsonValueAsString(val));
}
val = HashMapGet(json, "visibility");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
*errp = "RoomCreateRequest.visibility must be of type RoomVisibility.";
return 0;
}
enumParseRes = RoomVisibilityFromStr(JsonValueAsString(val));
if (enumParseRes == -1)
{
*errp = "Invalid value for RoomCreateRequest.visibility.";
return 0;
}
out->visibility = enumParseRes;
}
val = HashMapGet(json, "creation_content");
if (val)
{
if (JsonValueType(val) != JSON_OBJECT)
{
*errp = "RoomCreateRequest.creation_content must be of type object.";
return 0;
}
out->creation_content = JsonValueAsObject(JsonValueDuplicate(val));
}
val = HashMapGet(json, "is_direct");
if (val)
{
if (JsonValueType(val) != JSON_BOOLEAN)
{
*errp = "RoomCreateRequest.is_direct must be of type boolean.";
return 0;
}
out->is_direct = JsonValueAsBoolean(val);
}
val = HashMapGet(json, "name");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
*errp = "RoomCreateRequest.name must be of type string.";
return 0;
}
out->name = StrDuplicate(JsonValueAsString(val));
}
val = HashMapGet(json, "initial_state");
if (val)
{
if (JsonValueType(val) != JSON_ARRAY)
{
*errp = "RoomCreateRequest.initial_state must be of type [RoomStateEvent].";
return 0;
}
out->initial_state = ArrayCreate();
if (!out->initial_state)
{
*errp = "Failed to allocate memory for RoomCreateRequest.initial_state.";
return 0;
}
else
{
Array *arr = JsonValueAsArray(val);
size_t i;
for (i = 0; i <ArraySize(arr); i++)
{
JsonValue *v = ArrayGet(arr, i);
RoomStateEvent *parsed;
if (JsonValueType(v) != JSON_OBJECT)
{
*errp = "RoomCreateRequest.initial_state[] contains an invalid value.";
return 0;
}
parsed = Malloc(sizeof(RoomStateEvent));
if (!parsed)
{
*errp = "Unable to allocate memory for array value.";
return 0;
}
if (!RoomStateEventFromJson(JsonValueAsObject(v), parsed, errp))
{
RoomStateEventFree(parsed);
Free(parsed);
return 0;
}
ArrayAdd(out->initial_state, parsed);
}
}
}
val = HashMapGet(json, "power_level_content_override");
if (val)
{
if (JsonValueType(val) != JSON_OBJECT)
{
*errp = "RoomCreateRequest.power_level_content_override must be of type object.";
return 0;
}
out->power_level_content_override = JsonValueAsObject(JsonValueDuplicate(val));
}
val = HashMapGet(json, "room_alias_name");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
*errp = "RoomCreateRequest.room_alias_name must be of type string.";
return 0;
}
out->room_alias_name = StrDuplicate(JsonValueAsString(val));
}
val = HashMapGet(json, "preset");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
*errp = "RoomCreateRequest.preset must be of type RoomCreatePreset.";
return 0;
}
enumParseRes = RoomCreatePresetFromStr(JsonValueAsString(val));
if (enumParseRes == -1)
{
*errp = "Invalid value for RoomCreateRequest.preset.";
return 0;
}
out->preset = enumParseRes;
}
return 1;
}
HashMap *
RoomCreateRequestToJson(RoomCreateRequest *val)
{
HashMap *json;
if (!val)
{
return NULL;
}
json = HashMapCreate();
if (!json)
{
return NULL;
}
if (val->invite)
{
size_t i;
Array *jsonArr = ArrayCreate();
if (!jsonArr)
{
JsonFree(json);
return NULL;
}
for (i = 0; i < ArraySize(val->invite); i++)
{
ArrayAdd(jsonArr, JsonValueString(ArrayGet(val->invite, i)));
}
HashMapSet(json, "invite", JsonValueArray(jsonArr));
}
HashMapSet(json, "room_version", JsonValueString(val->room_version));
if (val->invite_3pid)
{
size_t i;
Array *jsonArr = ArrayCreate();
if (!jsonArr)
{
JsonFree(json);
return NULL;
}
for (i = 0; i < ArraySize(val->invite_3pid); i++)
{
ArrayAdd(jsonArr, JsonValueObject(RoomInvite3PidToJson(ArrayGet(val->invite_3pid, i))));
}
HashMapSet(json, "invite_3pid", JsonValueArray(jsonArr));
}
HashMapSet(json, "topic", JsonValueString(val->topic));
HashMapSet(json, "visibility", JsonValueString(RoomVisibilityToStr(val->visibility)));
HashMapSet(json, "creation_content", JsonValueObject(JsonDuplicate(val->creation_content)));
HashMapSet(json, "is_direct", JsonValueBoolean(val->is_direct));
HashMapSet(json, "name", JsonValueString(val->name));
if (val->initial_state)
{
size_t i;
Array *jsonArr = ArrayCreate();
if (!jsonArr)
{
JsonFree(json);
return NULL;
}
for (i = 0; i < ArraySize(val->initial_state); i++)
{
ArrayAdd(jsonArr, JsonValueObject(RoomStateEventToJson(ArrayGet(val->initial_state, i))));
}
HashMapSet(json, "initial_state", JsonValueArray(jsonArr));
}
HashMapSet(json, "power_level_content_override", JsonValueObject(JsonDuplicate(val->power_level_content_override)));
HashMapSet(json, "room_alias_name", JsonValueString(val->room_alias_name));
HashMapSet(json, "preset", JsonValueString(RoomCreatePresetToStr(val->preset)));
return json;
}
void
RoomCreateRequestFree(RoomCreateRequest *val)
{
if (!val)
{
return;
}
if (val->invite)
{
size_t i;
for (i = 0; i < ArraySize(val->invite); i++)
{
Free(ArrayGet(val->invite, i));
}
ArrayFree(val->invite);
}
Free(val->room_version);
if (val->invite_3pid)
{
size_t i;
for (i = 0; i < ArraySize(val->invite_3pid); i++)
{
RoomInvite3PidFree(ArrayGet(val->invite_3pid, i));
Free(ArrayGet(val->invite_3pid, i));
}
ArrayFree(val->invite_3pid);
}
Free(val->topic);
JsonFree(val->creation_content);
Free(val->name);
if (val->initial_state)
{
size_t i;
for (i = 0; i < ArraySize(val->initial_state); i++)
{
RoomStateEventFree(ArrayGet(val->initial_state, i));
Free(ArrayGet(val->initial_state, i));
}
ArrayFree(val->initial_state);
}
JsonFree(val->power_level_content_override);
Free(val->room_alias_name);
}
int
RoomInvite3PidFromJson(HashMap *json, RoomInvite3Pid *out, char **errp)
{
JsonValue *val;
int enumParseRes;
(void) enumParseRes;
if (!json | !out)
{
*errp = "Invalid pointers passed to RoomInvite3PidFromJson()";
return 0;
}
val = HashMapGet(json, "id_access_token");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
*errp = "RoomInvite3Pid.id_access_token must be of type string.";
return 0;
}
out->id_access_token = StrDuplicate(JsonValueAsString(val));
}
else
{
*errp = "RoomInvite3Pid.id_access_token is required.";
return 0;
}
val = HashMapGet(json, "address");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
*errp = "RoomInvite3Pid.address must be of type string.";
return 0;
}
out->address = StrDuplicate(JsonValueAsString(val));
}
else
{
*errp = "RoomInvite3Pid.address is required.";
return 0;
}
val = HashMapGet(json, "medium");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
*errp = "RoomInvite3Pid.medium must be of type Room3PidMedium.";
return 0;
}
enumParseRes = Room3PidMediumFromStr(JsonValueAsString(val));
if (enumParseRes == -1)
{
*errp = "Invalid value for RoomInvite3Pid.medium.";
return 0;
}
out->medium = enumParseRes;
}
else
{
*errp = "RoomInvite3Pid.medium is required.";
return 0;
}
val = HashMapGet(json, "id_server");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
*errp = "RoomInvite3Pid.id_server must be of type string.";
return 0;
}
out->id_server = StrDuplicate(JsonValueAsString(val));
}
else
{
*errp = "RoomInvite3Pid.id_server is required.";
return 0;
}
return 1;
}
HashMap *
RoomInvite3PidToJson(RoomInvite3Pid *val)
{
HashMap *json;
if (!val)
{
return NULL;
}
json = HashMapCreate();
if (!json)
{
return NULL;
}
HashMapSet(json, "id_access_token", JsonValueString(val->id_access_token));
HashMapSet(json, "address", JsonValueString(val->address));
HashMapSet(json, "medium", JsonValueString(Room3PidMediumToStr(val->medium)));
HashMapSet(json, "id_server", JsonValueString(val->id_server));
return json;
}
void
RoomInvite3PidFree(RoomInvite3Pid *val)
{
if (!val)
{
return;
}
Free(val->id_access_token);
Free(val->address);
Free(val->id_server);
}
int
Room3PidMediumFromStr(char *str)
{
if (StrEquals(str, "msisdn"))
{
return ROOM_3PID_MSISDN;
}
else if (StrEquals(str, "email"))
{
return ROOM_3PID_EMAIL;
}
else
{
return -1;
}
}
char *
Room3PidMediumToStr(Room3PidMedium val)
{
switch (val)
{
case ROOM_3PID_MSISDN:
return "msisdn";
case ROOM_3PID_EMAIL:
return "email";
default:
return NULL;
}
}
int
RoomCreatePresetFromStr(char *str)
{
if (StrEquals(str, "public_chat"))
{
return ROOM_CREATE_PUBLIC;
}
else if (StrEquals(str, "trusted_private_chat"))
{
return ROOM_CREATE_TRUSTED;
}
else if (StrEquals(str, "private_chat"))
{
return ROOM_CREATE_PRIVATE;
}
else
{
return -1;
}
}
char *
RoomCreatePresetToStr(RoomCreatePreset val)
{
switch (val)
{
case ROOM_CREATE_PUBLIC:
return "public_chat";
case ROOM_CREATE_TRUSTED:
return "trusted_private_chat";
case ROOM_CREATE_PRIVATE:
return "private_chat";
default:
return NULL;
}
}
int
RoomStateEventFromJson(HashMap *json, RoomStateEvent *out, char **errp)
{
JsonValue *val;
int enumParseRes;
(void) enumParseRes;
if (!json | !out)
{
*errp = "Invalid pointers passed to RoomStateEventFromJson()";
return 0;
}
val = HashMapGet(json, "content");
if (val)
{
if (JsonValueType(val) != JSON_OBJECT)
{
*errp = "RoomStateEvent.content must be of type object.";
return 0;
}
out->content = JsonValueAsObject(JsonValueDuplicate(val));
}
else
{
*errp = "RoomStateEvent.content is required.";
return 0;
}
val = HashMapGet(json, "state_key");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
*errp = "RoomStateEvent.state_key must be of type string.";
return 0;
}
out->state_key = StrDuplicate(JsonValueAsString(val));
}
val = HashMapGet(json, "type");
if (val)
{
if (JsonValueType(val) != JSON_STRING)
{
*errp = "RoomStateEvent.type must be of type string.";
return 0;
}
out->type = StrDuplicate(JsonValueAsString(val));
}
else
{
*errp = "RoomStateEvent.type is required.";
return 0;
}
return 1;
}
HashMap *
RoomStateEventToJson(RoomStateEvent *val)
{
HashMap *json;
if (!val)
{
return NULL;
}
json = HashMapCreate();
if (!json)
{
return NULL;
}
HashMapSet(json, "content", JsonValueObject(JsonDuplicate(val->content)));
HashMapSet(json, "state_key", JsonValueString(val->state_key));
HashMapSet(json, "type", JsonValueString(val->type));
return json;
}
void
RoomStateEventFree(RoomStateEvent *val)
{
if (!val)
{
return;
}
JsonFree(val->content);
Free(val->state_key);
Free(val->type);
}

View file

@ -85,6 +85,7 @@ ROUTE(RouteChangePwd);
ROUTE(RouteDeactivate);
ROUTE(RouteTokenValid);
ROUTE(RouteUserProfile);
ROUTE(RouteUserDirectory);
ROUTE(RouteRequestToken);
ROUTE(RouteUiaFallback);

View file

@ -0,0 +1,39 @@
/* Generated by j2s */
#ifndef TELODENDRIA_SCHEMA_CLIENTEVENT_H
#define TELODENDRIA_SCHEMA_CLIENTEVENT_H
#include <Array.h>
#include <HashMap.h>
#include <Int64.h>
typedef struct ClientEventUnsignedData
{
HashMap * redacted_because;
char * transaction_id;
HashMap * prev_content;
Int64 age;
} ClientEventUnsignedData;
typedef struct ClientEvent
{
Int64 origin_server_ts;
HashMap * content;
char * room_id;
char * sender;
char * state_key;
char * event_id;
char * type;
ClientEventUnsignedData _unsigned;
} ClientEvent;
extern int ClientEventUnsignedDataFromJson(HashMap *, ClientEventUnsignedData *, char **);
extern HashMap * ClientEventUnsignedDataToJson(ClientEventUnsignedData *);
extern void ClientEventUnsignedDataFree(ClientEventUnsignedData *);
extern int ClientEventFromJson(HashMap *, ClientEvent *, char **);
extern HashMap * ClientEventToJson(ClientEvent *);
extern void ClientEventFree(ClientEvent *);
#endif /* TELODENDRIA_SCHEMA_CLIENTEVENT_H */

View file

@ -0,0 +1,80 @@
/* Generated by j2s */
#ifndef TELODENDRIA_SCHEMA_FILTER_H
#define TELODENDRIA_SCHEMA_FILTER_H
#include <Array.h>
#include <HashMap.h>
#include <Int64.h>
typedef struct FilterEvent
{
Array * not_senders;
Int64 limit;
Array * senders;
Array * types;
Array * not_types;
} FilterEvent;
typedef enum FilterEventFormat
{
FILTER_FORMAT_FEDERATION,
FILTER_FORMANT_CLIENT
} FilterEventFormat;
typedef struct FilterRoomEvent
{
Array * not_rooms;
Array * not_senders;
Int64 limit;
Array * senders;
int include_redundant_members;
Array * types;
Array * rooms;
int lazy_load_members;
Array * not_types;
int contains_url;
int unread_thread_notifications;
} FilterRoomEvent;
typedef struct FilterRoom
{
Array * not_rooms;
FilterRoomEvent state;
int include_leave;
FilterRoomEvent timeline;
FilterRoomEvent account_data;
Array * rooms;
FilterRoomEvent ephemeral;
} FilterRoom;
typedef struct Filter
{
FilterEventFormat event_format;
FilterEvent presence;
FilterEvent account_data;
FilterRoom room;
Array * event_fields;
} Filter;
extern int FilterRoomFromJson(HashMap *, FilterRoom *, char **);
extern HashMap * FilterRoomToJson(FilterRoom *);
extern void FilterRoomFree(FilterRoom *);
extern int FilterEventFormatFromStr(char *);
extern char * FilterEventFormatToStr(FilterEventFormat);
extern int FilterEventFromJson(HashMap *, FilterEvent *, char **);
extern HashMap * FilterEventToJson(FilterEvent *);
extern void FilterEventFree(FilterEvent *);
extern int FilterFromJson(HashMap *, Filter *, char **);
extern HashMap * FilterToJson(Filter *);
extern void FilterFree(Filter *);
extern int FilterRoomEventFromJson(HashMap *, FilterRoomEvent *, char **);
extern HashMap * FilterRoomEventToJson(FilterRoomEvent *);
extern void FilterRoomEventFree(FilterRoomEvent *);
#endif /* TELODENDRIA_SCHEMA_FILTER_H */

View file

@ -0,0 +1,51 @@
/* Generated by j2s */
#ifndef TELODENDRIA_SCHEMA_PDUV1_H
#define TELODENDRIA_SCHEMA_PDUV1_H
#include <Array.h>
#include <HashMap.h>
#include <Int64.h>
typedef struct PduV1UnsignedData
{
Int64 age;
} PduV1UnsignedData;
typedef struct PduV1EventHash
{
char * sha256;
} PduV1EventHash;
typedef struct PduV1
{
Int64 origin_server_ts;
HashMap * content;
char * redacts;
char * sender;
Int64 depth;
Array * prev_events;
char * type;
PduV1UnsignedData _unsigned;
Array * auth_events;
char * room_id;
char * state_key;
HashMap * signatures;
char * event_id;
PduV1EventHash hashes;
} PduV1;
extern int PduV1EventHashFromJson(HashMap *, PduV1EventHash *, char **);
extern HashMap * PduV1EventHashToJson(PduV1EventHash *);
extern void PduV1EventHashFree(PduV1EventHash *);
extern int PduV1FromJson(HashMap *, PduV1 *, char **);
extern HashMap * PduV1ToJson(PduV1 *);
extern void PduV1Free(PduV1 *);
extern int PduV1UnsignedDataFromJson(HashMap *, PduV1UnsignedData *, char **);
extern HashMap * PduV1UnsignedDataToJson(PduV1UnsignedData *);
extern void PduV1UnsignedDataFree(PduV1UnsignedData *);
#endif /* TELODENDRIA_SCHEMA_PDUV1_H */

View file

@ -0,0 +1,50 @@
/* Generated by j2s */
#ifndef TELODENDRIA_SCHEMA_PDUV3_H
#define TELODENDRIA_SCHEMA_PDUV3_H
#include <Array.h>
#include <HashMap.h>
#include <Int64.h>
typedef struct PduV3UnsignedData
{
Int64 age;
} PduV3UnsignedData;
typedef struct PduV3EventHash
{
char * sha256;
} PduV3EventHash;
typedef struct PduV3
{
Int64 origin_server_ts;
HashMap * content;
char * redacts;
char * sender;
Int64 depth;
Array * prev_events;
char * type;
PduV3UnsignedData _unsigned;
Array * auth_events;
char * room_id;
char * state_key;
HashMap * signatures;
PduV3EventHash hashes;
} PduV3;
extern int PduV3EventHashFromJson(HashMap *, PduV3EventHash *, char **);
extern HashMap * PduV3EventHashToJson(PduV3EventHash *);
extern void PduV3EventHashFree(PduV3EventHash *);
extern int PduV3FromJson(HashMap *, PduV3 *, char **);
extern HashMap * PduV3ToJson(PduV3 *);
extern void PduV3Free(PduV3 *);
extern int PduV3UnsignedDataFromJson(HashMap *, PduV3UnsignedData *, char **);
extern HashMap * PduV3UnsignedDataToJson(PduV3UnsignedData *);
extern void PduV3UnsignedDataFree(PduV3UnsignedData *);
#endif /* TELODENDRIA_SCHEMA_PDUV3_H */

View file

@ -0,0 +1,82 @@
/* Generated by j2s */
#ifndef TELODENDRIA_SCHEMA_ROOMCREATE_H
#define TELODENDRIA_SCHEMA_ROOMCREATE_H
#include <Array.h>
#include <HashMap.h>
#include <Int64.h>
typedef enum Room3PidMedium
{
ROOM_3PID_MSISDN,
ROOM_3PID_EMAIL
} Room3PidMedium;
typedef enum RoomCreatePreset
{
ROOM_CREATE_PUBLIC,
ROOM_CREATE_TRUSTED,
ROOM_CREATE_PRIVATE
} RoomCreatePreset;
typedef struct RoomStateEvent
{
HashMap * content;
char * state_key;
char * type;
} RoomStateEvent;
typedef struct RoomInvite3Pid
{
char * id_access_token;
char * address;
Room3PidMedium medium;
char * id_server;
} RoomInvite3Pid;
typedef enum RoomVisibility
{
ROOM_PUBLIC,
ROOM_PRIVATE
} RoomVisibility;
typedef struct RoomCreateRequest
{
Array * invite;
char * room_version;
Array * invite_3pid;
char * topic;
RoomVisibility visibility;
HashMap * creation_content;
int is_direct;
char * name;
Array * initial_state;
HashMap * power_level_content_override;
char * room_alias_name;
RoomCreatePreset preset;
} RoomCreateRequest;
extern int RoomVisibilityFromStr(char *);
extern char * RoomVisibilityToStr(RoomVisibility);
extern int RoomCreateRequestFromJson(HashMap *, RoomCreateRequest *, char **);
extern HashMap * RoomCreateRequestToJson(RoomCreateRequest *);
extern void RoomCreateRequestFree(RoomCreateRequest *);
extern int RoomInvite3PidFromJson(HashMap *, RoomInvite3Pid *, char **);
extern HashMap * RoomInvite3PidToJson(RoomInvite3Pid *);
extern void RoomInvite3PidFree(RoomInvite3Pid *);
extern int Room3PidMediumFromStr(char *);
extern char * Room3PidMediumToStr(Room3PidMedium);
extern int RoomCreatePresetFromStr(char *);
extern char * RoomCreatePresetToStr(RoomCreatePreset);
extern int RoomStateEventFromJson(HashMap *, RoomStateEvent *, char **);
extern HashMap * RoomStateEventToJson(RoomStateEvent *);
extern void RoomStateEventFree(RoomStateEvent *);
#endif /* TELODENDRIA_SCHEMA_ROOMCREATE_H */

View file

@ -1,167 +0,0 @@
#!/usr/bin/env sh
#
# send-patch: "The Telodendria Patch Sender"
#
# This is a simple script for posting patch files to
# a single room(generally the Telodendria patch room.)
. "$(pwd)/tools/lib/common.sh"
# Path to the patch to send.
PATCHFILE="$1"
# Tries to decompose the name and the HS from an MXID using
# sed.
UR_NAME="$(echo "$MXID" | sed "s/\@\(.*\)\:\(.*\)/\1/")"
HS_NAME="$(echo "$MXID" | sed "s/\@\(.*\)\:\(.*\)/\2/")"
# Prompts the user for a password, while disabling echo-ing.
readpwd() {
printf "$1"
stty -echo -ctlecho
read -r "$2"
echo
stty echo ctlecho
}
# Makes an HTTP request, setting the RETURN variable for the
# actual reply from the server and the ERROR_CODE variable
# for a HTTP error code.
request() {
RETURN=$(http -i "$@" 2>/dev/null)
ERROR_CODE=$(echo "$RETURN" | head -n1 | awk '{print $2}')
RETURN=$(echo "$RETURN" | sed '1,/^[[:space:]]*$/d')
}
# Prompts user to login and gives out an access token to use
# in the ACCESS_TOKEN variable
matrix_login() {
# Check authentication methods
echo "Checking authentication methods..."
request "$HS_BASE/_matrix/client/v3/login"
AUTH_METHODS=$RETURN
if [ $ERROR_CODE -ne 200 ]; then
echo "Homeserver does not support login."
exit 1
fi
if ! echo "$AUTH_METHODS" | grep "m.login.password" >/dev/null; then
echo "Homeserver does not support password authentication."
exit 1
fi
# Homeserver does support password authentication, so request
# them one.
if [ -z "$MXPW" ]; then
readpwd "Enter your Matrix password: " MXPW
fi
# Tries to login using the "Telodendria Patch Script" device
# name
JSON=$(
printf '{'
printf ' "identifier": {'
printf ' "type": "m.id.user",'
printf ' "user": %s' "$(json -e $UR_NAME)"
printf ' },'
printf ' "initial_device_display_name": "Telodendria Patch Script",'
printf ' "type": "m.login.password",'
printf ' "password": %s' "$(json -e "$MXPW")"
printf '}'
)
request -X POST -d "$JSON" $HS_BASE/_matrix/client/v3/login
LOGIN="$RETURN"
if [ $ERROR_CODE -ne 200 ]; then
echo "Login failed."
echo "$RETURN"
exit 1
fi
ACCESS_TOKEN=$(echo "$LOGIN" | json -s "access_token->@decode")
}
# Logs out of Matrix using the ACFESS_TOKEN environment variable
matrix_logout() {
if [ -z "$ACCESS_TOKEN" ]; then
echo "No access token"
exit 1
fi
request -X POST -H "Authorization: Bearer $ACCESS_TOKEN" "$HS_BASE/_matrix/client/v3/logout"
LOGOUT=$RETURN
if [ $ERROR_CODE -ne 200 ]; then
echo "Logout failed."
echo "$RETURN"
exit 1
fi
echo "Logged out."
}
send_patch() {
if [ -z "$ACCESS_TOKEN" ]; then
matrix_login
DO_LOGOUT=1
fi
# We are sucessfully logged in as our user, now let's
# try to upload and post our patch
echo "$PATCHFILE"
request -X POST \
-H "Content-Type: text/x-patch" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-d "@$PATCHFILE" \
"$HS_BASE/_matrix/media/v3/upload"
MXCID=$RETURN
if [ $ERROR_CODE -ne 200 ]; then
echo "Upload failed."
echo "$RETURN"
matrix_logout
exit 1
fi
MXCID=$(echo "$MXCID" | json -s "content_uri->@decode")
echo "MXC ID: $MXCID"
JSON=$(
base=$(basename "$PATCHFILE")
printf '{'
printf ' "body": %s,' "$(json -e $base)"
printf ' "filename": %s,' "$(json -e $base)"
printf ' "info": {'
printf ' "mimetype": "text/x-patch",'
printf ' "size": %d' $(wc -c "$PATCHFILE" | awk '{print $1}')
printf ' },'
printf ' "msgtype": "m.file",'
printf ' "url": %s' "$(json -e $MXCID)"
printf '}'
)
http -X PUT -d "$JSON" -H "Authorization: Bearer $ACCESS_TOKEN" \
"$HS_BASE/_matrix/client/v3/rooms/$PATCHES_ROOM/send/m.room.message/$(date +%s)" \
2>/dev/null >/dev/null && echo "Patch sent."
# Log out if we generated an access token
if [ "$DO_LOGOUT" -eq "1" ]; then
matrix_logout
fi
}
# Check if the patch file is valid.
if [ "$(basename "$PATCHFILE" .patch)" = "$PATCHFILE" ] || [ ! -f "$PATCHFILE" ]; then
echo "Format: $0 file.patch"
exit 1
fi
echo "Sending file '$PATCHFILE'"
echo "Checking homeserver's real address using .well-known..."
request "https://$HS_NAME/.well-known/matrix/client"
case "$ERROR_CODE" in
"200")
WELL_KNOWN=$RETURN
if [ -z "$WELL_KNOWN" ]; then
echo "well-known test returned 200 but no correct input was given."
exit 1
fi
# well-known entry is correct, we can now store our base endpoint
HS_BASE=$(printf "$WELL_KNOWN" | json -s "m.homeserver->base_url->@decode") && send_patch
;;
*)
echo "$ERROR_CODE"
echo "well-known test failed."
printf "Please enter your homeserver base URL: "
read -r HS_BASE
echo
send_patch
;;
esac

View file

@ -1,148 +0,0 @@
#!/usr/bin/env sh
#
# tp: "Telodendria Patch"
#
# This script is used to manage the patch queue.
. "$(pwd)/tools/lib/common.sh"
if [ -z "$TELODENDRIA_PUB" ]; then
echo "TELODENDRIA_PUB not set."
exit 1
fi
TP_DIR="$TELODENDRIA_PUB/patches"
if [ ! -d "$TP_DIR" ]; then
echo "$TP_DIR does not exist."
exit 1
fi
matrix_send() {
msg="$1"
if [ ! -z "$msg" ]; then
(
printf '{'
printf '"body":'
json -e "$msg"
printf ',"formatted_body":'
json -e "$msg"
printf ',"format":"org.matrix.custom.html",'
printf '"msgtype":"m.text"'
printf '}'
) > /tmp/tp-$$
http -X PUT -d @/tmp/tp-$$ "$HOMESERVER/client/v3/rooms/$PATCHES_ROOM/send/m.room.message/$(date +%s)?access_token=$ACCESS_TOKEN"
rm /tmp/tp-$$
fi
}
case "$1" in
"ingress")
timeline="/tmp/timeline.json"
http "$HOMESERVER/client/v3/sync?access_token=$ACCESS_TOKEN" |
json -s "rooms->join->${PATCHES_ROOM}->timeline" >"$timeline"
length=$(cat "$timeline" | json -s "events->@length")
i=0
while [ $i -lt $length ]; do
content=$(cat "$timeline" | json -s "events[$i]->content->^body->^formatted_body")
i=$((i + 1))
type=$(echo "$content" | json -s "msgtype->@decode")
if [ "$type" != "m.file" ]; then
continue
fi
size=$(echo "$content" | json -s "info->size")
if [ "$size" -gt "$MAX_SIZE" ]; then
continue
fi
file=$(echo "$content" | json -s "filename->@decode")
ext=$(echo "$file" | rev | cut -d '.' -f 1 | rev)
if [ "$ext" != "patch" ]; then
continue
fi
url=$(echo "$content" | json -s "url->@decode")
id=$(echo "$url" | cut -d '/' -f 4)
if [ -f "$TP_DIR/ingress/$id.patch" ]; then
continue
fi
server=$(echo "$url" | cut -d '/' -f 3)
if ! http "$HOMESERVER/media/v3/download/$server/$id" > "$TP_DIR/ingress/$id.patch"; then
rm "$TP_DIR/ingress/$id.patch"
echo "Failed to fetch mxc://$server/$id."
echo "Will try again next time."
continue
fi
count=$(cat "$TP_DIR/count.txt")
count=$((count + 1))
cp "$TP_DIR/ingress/$id.patch" "$TP_DIR/p/$count.patch"
(
cd "$TP_DIR/queued"
ln -s "../p/$count.patch" "$count.patch"
)
echo "$count" >"$TP_DIR/count.txt"
matrix_send "Queued <code>$file</code> as <a href=\"https://telodendria.io/patches/p/$count.patch\">#$count</a>" >/dev/null
done
;;
"queue")
find "$TP_DIR/queued" -name '*.patch' | while IFS= read -r patch; do
n=$(basename "$patch" .patch)
echo "Patch #$n:"
head -n3 "$patch"
echo
done
;;
"view")
if [ -f "$TP_DIR/queued/$2.patch" ]; then
less "$TP_DIR/queued/$2.patch"
else
echo "Patch #$2 doesn't exist in the queue."
exit 1
fi
;;
"apply")
if [ -f "$TP_DIR/queued/$2.patch" ]; then
patch <"$TP_DIR/queued/$2.patch"
else
echo "Patch #$2 doesn't exist in the queue."
exit 1
fi
;;
"reverse")
if [ -f "$TP_DIR/queued/$2.patch" ]; then
patch -R <"$TP_DIR/queued/$2.patch"
else
echo "Patch #$2 doesn't exist in the queue."
exit 1
fi
;;
"accept" | "reject")
if [ -f "$TP_DIR/queued/$2.patch" ]; then
mv "$TP_DIR/queued/$2.patch" "$TP_DIR/${1}ed/$2.patch"
msg="Patch <a href=\"https://telodendria.io/patches/p/$2.patch\">#$2</a> was marked as ${1}ed."
msgFile="/tmp/patchmsg-$(date +%s).txt"
$EDITOR "$msgFile"
if [ -f "$msgFile" ]; then
msg="$msg<br><blockquote>$(cat $msgFile)<br>&mdash;$DISPLAY_NAME ($MXID)</blockquote>"
fi
matrix_send "$msg"
else
echo "Patch #$2 doesn't exist in the queue."
exit 1
fi
;;
*)
echo "No action specified."
exit 1
;;
esac