# framadatectl

This python program offers both a Python API and a shell command allowing you
to:
- read data from a Framadate (via its public link)
- manage a Framadate (via its admin link)

Framadate is a service from Framasoft (https://framadate.org) but it can also be
self-hosted.

## Installation

### From PyPI

```bash
pip install framadatectl
```

### From sources

```bash
git clone https://gitlab.com/albinou/python-framadatectl.git
cd python-framadatectl
pip install .
```

### Use framadatectl without installation

```bash
git clone https://gitlab.com/albinou/python-framadatectl.git
cd python-framadatectl/src
python -m framadatectl [arguments]
```

## Command line

### Basic usage

Here are a few examples of command line usage. `URL` is to be replaced with a
valid Framadate link. It can either be:
- a public link of the form https://framadate.org/IDENTIFIER
- an admin link of the form https://framadate.org/ADMIN_IDENTIFIER/admin

Examples:

1. Display all poll results, i.e. all date slots:

```bash
shell> framadatectl --url=URL show all-slots
2021-09-01: [Bob, Alice, John] (3 vote(s))
2021-09-08: [Bob, Alice] (2 vote(s))
2021-09-15: [John] (1 vote(s))
```

2. Display the next date slot (let's consider today date is 2021-09-05):

```bash
shell> framadatectl --url=URL show next-slot
2021-09-08: [Bob, Alice] (2 vote(s))
```

3. Check if at least 3 people voted for the next date slot:

```bash
shell> framadatectl --url=URL --min-votes=3 check next-slot
2021-09-08: KO (1 missing vote(s))
```

4. Check if at least 3 people voted for the date slot "2021-09-01":

```bash
shell> framadatectl --url=URL --min-votes=3 check vote 2021-09-01
2021-09-01: OK (3 vote(s))
```

5. Add a new date slot for 2021-09-22:

```bash
shell> framadatectl --url=URL add slot 2021-09-22
2021-09-22: added
```

6. Delete old date slots (let's consider today date is 2021-09-05):

```bash
shell> framadatectl --url=URL delete old-slots
2021-09-01: deleted
```

7. Much more commands and sub-commands can be found in the usage:

```bash
shell> framadatectl --help
usage: __main__.py [-h] [-c CONFIG] [--url URL] [--min-votes MIN_VOTES] [--max-votes MAX_VOTES] [--quiet] [-v]
                   {show,check,add,delete,job} ...

A tool to manage a Framadate

optional arguments:
  -h, --help            show this help message and exit
  -c CONFIG, --config CONFIG
                        Path to a config file
  --url URL             Full Framadate URL (admin or public)
  --min-votes MIN_VOTES
                        Minimum number of required votes (used by check commands)
  --max-votes MAX_VOTES
                        Maximum number of required votes (used by check commands)
  --quiet               Make the output less verbose
  -v, --version         show program's version number and exit

commands:
  {show,check,add,delete,job}
    show                Show poll results
    check               Check poll results
    add                 Add new date slots
    delete              Delete date slots
    job                 Run a job from a config file
```

### Using a configuration file

Having to specify the URL, the min-votes and max-votes values on the command can
be cumbersome. Here is where the configuration comes in!

The configuration can be passed to `framadatectl` thanks to the `--config`
option. The configuration file follows the
[YAML](https://en.wikipedia.org/wiki/YAML) syntax.

Here a basic `config.yaml` file example:

```yaml
configuration:
  url: https://framadate.org/IDENTIFIER
  constraints:
    votes:
      min: 3
      max: 3
      moment_regexp: '^(?P<nb>\d+) people$'
```

Note that `url` is the only required field.

Beware to keep the space indentation.

### Running jobs

#### Overview

Jobs can defined in the configuration and then run like this:

```bash
framadatectl --config=config.yaml job JOB_NAME
```

The syntax for defining jobs has been highly inspired by the
[Home Assistant](https://www.home-assistant.io) project.
Here is an example of job named `JOB_ID`:

```yaml
job:
  - id: JOB_ID
    condition:
      - condition: CONDITION_1
        # arguments for the condition rule
      - condition: CONDITION_2
        # arguments for the condition rule
      [...]
    action:
      - action: ACTION_1
        # arguments for the action rule
      - action: ACTION_2
        # arguments for the action rule
```

Calling `framadatectl --config=config.yaml job JOB_ID` will run `ACTION_1` and
`ACTION_2` if both `CONDITION_1` and `CONDITION_2` are true.
Definitions of supported conditions and actions are available below.

#### Conditions

Conditions are optional and allows to run a job only if all conditions are met.
Here is a list of supported conditions.

##### slots

The `slots` condition allows to verify conditions on a set of slots.
This condition requires 2 arguments:
- `slots`: filter the date slots
- `verify`: condition to check on the filtered slots

`verify` can specify:
- `votes` / `less` or `more`: number of expected votes
- `constraints`: a boolean specifying whether the check of config constraints
must return True or False

For example, the following `slots` condition is met if the next date slot has
less than 3 participants and more than 2 participants.

```yaml
job:
  - id: JOB_NAME
    condition:
      - condition: slots
        slots: next-slot
        verify:
          votes:
            less: 3
            more: 2
    action:
      - action: ACTION
```

This other condition example checks whether the config constraints are met.

```yaml
job:
  - id: JOB_NAME
    condition:
      - condition: slots
        slots: next-slot
        verify:
          constraints: True
    action:
      - action: ACTION
```

It could be used to warn people that there is not enough people for this date.

#### Actions

##### command

This `command` action can be used to run commands that are usually run from a
shell.
This action requires 2 arguments:
- `command`: a command like `show`, `check`, `add` or `delete`
- `subcommand`: the subcommand of the given command

For example, the following `command` action deletes all old date slots.

```yaml
job:
  - id: JOB_NAME
    action:
      - action: command
        command: delete
        subcommand: old-slots
```

##### backup

The `backup` action allows to backup some votes.
This action requires 2 arguments :
- `slots`: filter the date slots to backup
- `mode`: file mode ('w' for truncating the file, 'a' for appending)
- `filepath`: location of the backup file

For example, the following `backup` action appends the outdated votes to the
backup.txt file.

```yaml
    action:
      - action: backup
        slots: old-slots
        mode: a
        filepath: /PATH/TO/backup.txt
```

Output file will have the following format (one information per line, allowing
the file to be easily parsed with grep or sed):

```
# 2021-09-01
Bob
Alice
John
```

##### email

The `email` action allows to send an email.
This action requires 2 arguments:
- `slot`: date slot whose data can be used in the email content
- `content`: Email content

For example, the following `email` action sends an email to dest@example.org
with details on the next comming date slot:

```yaml
job:
  - id: JOB_NAME
    action:
      - action: email
        slot: next-slot
        content: |
          From: me@example.org
          To: dest@example.org
          Content-Type: text/plain; charset=utf-8
          Content-Language: fr-FR
          Subject: Information on date slot {date:%A %d %B}
          
          Hello,
          
          There is {nb_votes} for {date:%A %d %B}.
```

This command requires to add the smtp server into your configuration:

```yaml
configuration:
  url: https://framadate.org/IDENTIFIER
  email:
    smtp_host: smtp.example.com
    smtp_port: 465     # default value is 25
    smtp_security: SSL # valid values are SSL, STARTTLS
    smtp_user: bob
    smtp_password: Alice
```

Note that `smtp_port`, `smtp_security`, `smtp_user` and `smtp_password` options are optional (can be useful if your SMTP server does not require any authentication and/or does not use any security mode).

## Library

This python package provides a Framadate class that you can instantiate.

Here is an example below:

```python
from datetime import date
from framadatectl import framadate

framadate = framadate.Framadate(config.get_url())
votes = framadate.get_votes(date(2021, 9, 1))
for v in votes:
    print(v.get_name())
```

Take a look at the [source code](https://gitlab.com/albinou/python-framadatectl/-/blob/master/src/framadatectl/framadate.py)
for more details.

## How does this work?

This programs works by:
- fetching a Framadate CSV file with the poll results
- running some well formated HTTP GET or POST requests
- parsing the HTML page in order to get some advanced operations URLs
  (with the Python Beautiful Soup library)
