ericferon / glpi-webhook

GNU General Public License v2.0
20 stars 5 forks source link

webhook - zulip #42

Open Smiley-k opened 9 months ago

Smiley-k commented 9 months ago

Tried to use webhook sending to send messages to zulip (I did a migration from RC to zulip), but I found that there is no way to do the sending as zulip has a completely different integration method.

https://zulip.com/api/send-message

# For stream messages
curl -X POST https://yourZulipDomain.zulipchat.com/api/v1/messages \
    -u BOT_EMAIL_ADDRESS:BOT_API_KEY \
    --data-urlencode type=stream \
    --data-urlencode 'to="Denmark"' \
    --data-urlencode topic=Castle \
    --data-urlencode 'content=I come not, friends, to steal away your hearts.'

# For direct messages
curl -X POST https://yourZulipDomain.zulipchat.com/api/v1/messages \
    -u BOT_EMAIL_ADDRESS:BOT_API_KEY \
    --data-urlencode type=direct \
    --data-urlencode 'to=[9]' \
    --data-urlencode 'content=With mirth and laughter let old wrinkles come.'

It would be cool if it was possible to configure webhook for zulip - is it possible to add this in a future update?

netruner commented 8 months ago

@Smiley-k Have you been able to run with zulip? I'm trying mainly on mentions in tickets. Unfortunately no reaction

Smiley-k commented 8 months ago

@Smiley-k Have you been able to run with zulip? I'm trying mainly on mentions in tickets. Unfortunately no reaction

No, I failed unfortunately, but I'm looking for solutions

netruner commented 7 months ago

I have an idea and I will test the solution with node-red

olivierbicler commented 7 months ago

For the record, we used a mini script in Python (with a Flask container in our case) to properly add notifications in Zulip. This can be much simpler (we don't display all tickets from glpi in Zulip). I left our specific code as example.

import json
import zulip
from datetime import datetime
from bs4 import BeautifulSoup
from markdownify import markdownify as md
from typing import Any

app = Flask(__name__)

ZULIP_CHANNEL = "BSG"
GLPI_FORMS = {
    "Imprimante": "content",
    "Combien d'imprimantes fonctionnent encore ?": "content",
    "Description du problème": ("content", "md"),
    "Votre nom": "author",
    "Ordinateur qui pose problème": "content",
    "Déclaration du Hors Service dans Viverrin": "content",
    "Type du problème": ("content", "md"),
    "Nom de l'imprimante": "content",
    "Où se trouve l'ordinateur ?": "content",
    "Quelle page a posé problème": "content",
    "Le problème porte sur": "content",
    "Niveau de blocage": "content",
}

@app.route("/api", methods=["GET"])
def get_my_ip_api():
    # print(request.environ)
    # Are we behind RProxy ?
    if 'HTTP_X_REAL_IP' in request.environ:
        ip = request.environ['HTTP_X_REAL_IP']
    else:
        ip = request.environ['REMOTE_ADDR']

    return jsonify({'ip': ip},), 200

@app.route("/glpi", methods=["POST"])
def post_zulip():
    try:
        data = request.data.decode('utf-8')
        data_json = json.loads(data)
        # Exclude SP > lecteur, + Pro
        # Should we handle this ticket ?
        ticket_type, ticket_category = (x.strip() for x in data_json['title'].split('-'))
        print(f"{ticket_type=}, {ticket_category=}, action={data_json['action']}")
        if ticket_type.strip() != 'Ticket SP' or ticket_category.strip() == 'Lecteur':
            res = jsonify({'msg': 'This ticket will not be treated'})
            return res, 200

        soup = BeautifulSoup(data_json['content'], 'html.parser')
        response = {'content': '', 'author': ''}
        for i in soup.div.find_all('div'):
            if len(i.contents) > 1:
                # Remove question's index plus 3 last characters (' : ')
                question = i.b.string.split(' ', 1)[1][:-3]
                if question in GLPI_FORMS:
                    content = "".join([str(j) for j in list(i.children)[1:]])
                    if isinstance(GLPI_FORMS[question], tuple):
                        var_name = GLPI_FORMS[question][0]
                        apply_func = GLPI_FORMS[question][1]
                    else:
                        var_name = GLPI_FORMS[question]
                        apply_func = None
                    if apply_func is not None:
                        if apply_func == 'md':
                            content = md(str(content), strip=['p'])
                        else:
                            res = jsonify({'msg': f'Unknown func to apply'})
                            return res, 500
                    if var_name == 'content':
                        response['content'] = f"{response['content']}**{question}** : {content}\n"
                    else:
                        response[GLPI_FORMS[question]] = content
        solution = None
        if data_json['action'] == 'Ticket résolu':
            if 'solution.description' in data_json and len(data_json['solution.description']) > 0:
                soup = BeautifulSoup(data_json['solution.description'], 'html.parser')
                solution = "".join([str(j).strip(" \n") for j in list(soup.children)])
            else:
                solution = "(Non spécifiée)"
        if solution:
            response['content'] = f"{response['content']}\n**Solution apportée** : {md(solution)}\n"
        if data_json['author'] == '':
            author = response['author'] if 'author' in response and response['author'] != '' else 'Anonyme'
        else:
            author = data_json['author']
        stripped_id = data_json['id'].lstrip('0')
        creation_date = datetime.strptime(data_json['creation'], "%d-%m-%Y %H:%M").strftime("%d/%m")
        topic_name = f"{creation_date} : {ticket_category} ({stripped_id})"
        client = zulip.Client(config_file="/root/.zuliprc")
        if data_json['action'] == 'Nouveau ticket':
            content = f"Ticket **{ticket_category}** ouvert par **{author}** ([Lien](https://support.bsg/glpi/front/ticket.form.php?id={data_json['id']}))\n{response['content']}"
        elif data_json['action'] == 'Ticket résolu':
            content = f"Résolution du ticket {data_json['id']} **{ticket_category}** ouvert par **{author}** ([Lien](https://support.bsg/glpi/front/ticket.form.php?id={data_json['id']}))\n{response['content']}"
        elif data_json['action'] == 'Nouvel utilisateur responsable':
            content = f"Le ticket {stripped_id} **{ticket_category}** ouvert par **{author}** est pris en charge par {data_json['assigntousers']} ! ([Lien](https://support.bsg/glpi/front/ticket.form.php?id={data_json['id']}))"
        else:
            content = f"Ticket **{ticket_category}** mis à jour ([Lien](https://support.bsg/glpi/front/ticket.form.php?id={data_json['id']}))\n{response['content']}"
        data = {'type': 'stream', 'to': ZULIP_CHANNEL, 'topic': topic_name, 'content': content}
        res = client.send_message(data)
        if data_json['action'] == 'Ticket résolu':
            # Mark topic as solved : simply prefix topic with "✔ ")
            zulip_request: Dict[str, Any] = {
                "anchor": "oldest",
                "num_after": 1,
                "num_before": 0,
                "narrow": [
                    {"operator": "stream", "operand": ZULIP_CHANNEL},
                    {"operator": "search", "operand": topic_name},
                ]
            }
            result = client.get_messages(zulip_request)
            data = {
                "message_id": result['messages'][0]['id'],
                "topic": f"✔ {topic_name}",
                "propagate_mode": "change_all",
            }
            res = client.update_message(data)

        return res, 200
    except Exception as e:
        print(e)
        return jsonify({'msg', "Une erreur a eu lieu : voir les logs dans Wimi"}), 500

if __name__ == '__main__':
    app.run()