Skip to content

Instantly share code, notes, and snippets.

@alanljj
Forked from yelizariev/$WELCOME.markdown
Created May 11, 2024 00:25
Show Gist options
  • Save alanljj/31d6f79539338b1ca348011c6c746c01 to your computer and use it in GitHub Desktop.
Save alanljj/31d6f79539338b1ca348011c6c746c01 to your computer and use it in GitHub Desktop.
Sync 🪬 Studio

¡Welcome!

Let me tell you why you are here...

You are here because you've heard about the free Odoo 🪬 Connectors that work like magic... Good news! You're on the right path.

First things first, you need Odoo (Community, Enterprise or the Pirate Edition — it doesn't really matter). Second, make sure you understand how to install custom Odoo modules. Finally, you must know the special magic involved in installing the great queue_job module.

Ready?

Let's go!

  1. Fork this gist.
  2. Install Sync 🪬 Studio.
  3. Navigate to the Sync 🪬 Studio menu, click [NEW] button, paste the link to your gist, and click [IMPORT].


image

Demo: https://odoomagic.com/demo.mp4

Great!

We use telegram integration to demonstrate basic tools provided by Sync 🪬 Studio. Alternatively, you can check other integrations made by the Cyber ✨ Pirates:

  • TODO1
  • TODO2
  • TODO3

image

Good luck!

Telegram ✈️ Bot

This project implements a Telegram Bot that communicates with Odoo.

For the initial setup, navigate to the SECRETS.🔐 tab.

If you need any help,
please contact us by email at info@odoomagic.com
or join our Telegram ✈️ Group.

How it works

Webhook

The Webhook Trigger waits for an update from Telegram. Once it happens, the action depends on the message text:

  • Message /start ➡️ sends a welcome message and creates a partner with an Internal Reference equal to <TELEGRAM_USER_ID>@telegram.

  • Any other message ➡️ attaches a copy of the message to the partner with the corresponding Internal Reference.

DB Trigger

Odoo res.partner records with the correct Internal Reference values are set up to await messages with the /telegram prefix. When that happens, Odoo performs the following actions:

  • The message copy (after removing the prefix) is sent to the corresponding Telegram user via the Telegram Bot.
  • A new report message is attached to the partner record.

For more details check the task 🦋 "Send response via Odoo".

Further Reading

Are you eager to learn more about Sync 🪬 Studio possibilities? Check the detailed documentation:

Have a nice day!

// Ivan Kropotkin

Evaluation Context

Development becomes much easier when programmers have the right tools available. Here are the tools accessible during task code execution:

  • MAGIC.*: Basic predefined tools (check the sync/doc/MAGIC.rst file for details)
  • CORE.*: Core project tools defined in core.py
  • LIB.*: Additional project tools defined in library.py
  • PARAMS.*: Project settings and templates
  • WEBHOOKS.*: Project webhooks, i.e. Odoo URLs for receiving notifications from external systems

🦋 core.py

The core.py file contains the project's most important code, which can be used by other parts of the project.

core.py has unique access:

  • import XXX statements (other custom code cannot import packages for security reasons).
  • SECRETS.*: Holds the project's private keys.

Warning! The core.py file should remain clean and minimal. Always fork the gist and review core.py before using it in production!

🦋 library.py

The library.py file also contains custom code, but unlike core.py, it has no direct access to SECRETS.* values or the import statement. Instead, library.py can use PARAMS.* and CORE.* to create development helpers.

Settings

Every Sync Project has a set of customizable parameters. These parameters are specified using the YAML front matter format, similar to Jekyll. The markdown body provides concise documentation.

🦋 settings.markdown 🔧

Basic key-value parameters.

🦋 settings.templates.markdown ✏️

Multi-line parameters, such as message templates automatically sent to customers on specific events (cron, database updates, webhook notifications, or when the admin clicks the Magic ✨ Button).

🦋 settings.secrets.markdown 🔐

Protected parameters with limited access (e.g., integration tokens).

Reminder: Never paste your secret keys into a gist.

🦋 task.XXX.py

A task is a piece of code that is dynamically executed by certain triggers. Each trigger can run only one task. There are four types of triggers:

  • Manual Trigger: Triggered when a user clicks the corresponding button on the Sync Project form.
  • Cron Trigger: For instance, "Execute the task every hour."
  • DB Trigger: For example, "Execute the task when a res.partner is created."
  • Webhook Trigger: For example, "Execute the task upon receiving a message via the Telegram bot."

Triggers are defined through a YAML structure within a multiline comment at the top of the Python task file.

"""
TITLE: "Name of the task"
MAGIC_BUTTON: MANUAL_TRIGGER_NAME
CRON:
  - name: CRON
    interval_number: 15
    interval_type: minutes
WEBHOOK:
  - name: WEBHOOK_NAME_1
    webhook_type: json
  - name: WEBHOOK_NAME_2
    webhook_type: http
DB_TRIGGERS:
  - name: ON_PARTNER_CREATED
    model: res.partner
    trigger: on_create
"""

The task code should include at least one of the following functions:

def handle_button():

Function to handle manual trigger tasks.

def handle_cron():

Function to handle cron trigger tasks.

def handle_db(records):

  • records: Odoo records that triggered the current task.

def handle_webhook(httprequest):

  • httprequest: Provides detailed information about the request.

Response for JSON webhook:

  • return json_data

Response for HTTP webhook:

  • return data_str
  • return data_str, status
  • return data_str, status, headers

Example: return "<h1>Success!</h1>", 200, [('Content-Type', 'text/html')]

# https://github.com/eternnoir/pyTelegramBotAPI
import telebot
from lxml.html.clean import Cleaner
if SECRETS.TELEGRAM_BOT_TOKEN:
bot = telebot.TeleBot(token=SECRETS.TELEGRAM_BOT_TOKEN)
else:
raise Exception("Telegram bot token is not set")
def sendMessage(chat_id, *args, **kwargs):
MAGIC.log_transmission(
"Message to %s@telegram" % chat_id, MAGIC.json.dumps([args, kwargs])
)
bot.send_message(chat_id, *args, **kwargs)
def setWebhook(*args, **kwargs):
MAGIC.log_transmission("Telegram->setWebhook", MAGIC.json.dumps([args, kwargs]))
bot.set_webhook(*args, **kwargs)
def parse_data(data):
return telebot.types.Update.de_json(data)
# CORE.*
export(Cleaner,
telegram=MAGIC.AttrDict(sendMessage, setWebhook, parse_data)
)
def user2name(user):
if user.username:
return '@%s' % (user.username)
name = user.first_name
if user.last_name:
name += ' %s' % user.last_name
return name
def html_sanitize_telegram(html):
allowed_tags = set({"b", "i", "u", "s", "a", "code", "pre"})
cleaner = CORE.Cleaner(safe_attrs_only=True, safe_attrs=set(), allow_tags=allowed_tags, remove_unknown_tags=False)
html = cleaner.clean_html(html)
# remove surrounding div
return html[5:-6]
# LIB.*
export(user2name, html_sanitize_telegram)

MIT 🪬 License

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 substantial 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.

PARTNER_NAME_PREFIX
Telegram:

TELEGRAM_BOT_TOKEN

Initial Setup

  1. Open Telegram and go to @BotFather.
  2. Send the message /new.
  3. Follow @BotFather's instructions carefully to obtain your TELEGRAM_BOT_TOKEN.

Once you're done, you're ready to open the task 🦋 "Setup" and click the Magic ✨ Button.

Troubleshooting

  • Enable Odoo 🙈 Debug mode.
  • Go to the menu [[ Settings ]] >> Technical >> Parameters >> System Parameters.
  • Check that the web.base.url parameter is correctly configured, ensuring it's not set to localhost and includes the https prefix. The URL should be accessible via the internet. If you're using a local Odoo instance, you can still set up an HTTPS connection. For details, see the sync/README.rst file in the Sync 🪬 Studio repository.
  • Verify that the project isn't archived.
TELEGRAM_WELCOME_MESSAGE TELEGRAM_MESSAGE_SENT
Hello! How can I help you today? We are here to assist you with your needs. Thank you for contacting us!
The following message has been sent to Telegram 👍 <br/> <br/>

TELEGRAM_WELCOME_MESSAGE

The message is sent on to user when they open Telegram ✈️ Bot for the first time.

TELEGRAM_MESSAGE_SENT

The message is used to post a message at the partner form

"""
TITLE: "Process Telegram Messages"
WEBHOOK:
- name: TELEGRAM
webhook_type: json
"""
def handle_webhook(httprequest):
data = MAGIC.json.loads(httprequest.data.decode("utf-8"))
MAGIC.log("Raw data: %s" % data, MAGIC.LOG_DEBUG)
update = CORE.telegram.parse_data(data)
message = update.message or update.edited_message
is_edit = bool(update.edited_message)
user_ref = "%s@telegram" % message.from_user.id
partner = MAGIC.env["res.partner"].search([("ref", "=", user_ref)])
if not partner:
name = "%s%s" % (PARAMS.PARTNER_NAME_PREFIX, LIB.user2name(message.from_user))
partner = MAGIC.env["res.partner"].create({"name": name, "ref": user_ref})
odoo_message_text = ""
if message.text == "/start":
CORE.telegram.sendMessage(message.chat.id, PARAMS.TELEGRAM_WELCOME_MESSAGE)
odoo_message_text = "From Telegram:"
elif is_edit:
odoo_message_text = "Telegram user has updated their message:"
odoo_message_text = "%s\n\n%s" % (odoo_message_text, message.text)
partner.message_post(body=odoo_message_text)
"""
TITLE: "Send response via Odoo"
DB_TRIGGERS:
- name: ON_MESSAGE_POSTED
model: mail.message
trigger: on_create
filter_domain: '[["model","=","res.partner"],["body","ilike","/telegram"]]'
"""
def handle_db(records):
# records are instances of mail.message
for r in records:
if not MAGIC.html2plaintext(r.body or "").startswith("/telegram"):
continue
partner = MAGIC.env["res.partner"].browse(r.res_id)
user_ref = partner.ref or ""
if not user_ref.endswith("@telegram"):
continue
telegram_user_id = user_ref.split("@telegram")[0]
telegram_message_html = LIB.html_sanitize_telegram(r.body)
telegram_message_html = telegram_message_html.replace("/telegram", "")
CORE.telegram.sendMessage(telegram_user_id, telegram_message_html)
odoo_message_text = "%s%s" % (PARAMS.TELEGRAM_MESSAGE_SENT, telegram_message_html)
partner.message_post(body=odoo_message_text)
"""
TITLE: "Setup"
MAGIC_BUTTON: SETUP_TELEGRAM
"""
def handle_button():
CORE.telegram.setWebhook(WEBHOOKS.TELEGRAM, allowed_updates=["message", "edited_message"])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment