├── .DINAR
└── config.yaml
├── .github
└── workflows
│ └── DINAR-readme.yml
├── .travis.yml
├── README.md
├── requirements.txt
├── telegram
├── README.rst
├── __init__.py
├── __manifest__.py
├── controllers
│ ├── __init__.py
│ └── main.py
├── data
│ ├── base_action_rules.xml
│ ├── commands.xml
│ ├── config_parameter.xml
│ └── ir_action_server.xml
├── doc
│ ├── changelog.rst
│ └── index.rst
├── i18n
│ └── ru.po
├── security
│ ├── ir.model.access.csv
│ └── telegram_security.xml
├── static
│ └── description
│ │ ├── base_action_rule_form.png
│ │ ├── base_action_rule_form_2.png
│ │ ├── icon.png
│ │ ├── index.html
│ │ ├── ir_cron_form.png
│ │ ├── mail.png
│ │ ├── new_issues.png
│ │ ├── pipeline-photo.jpg
│ │ ├── pipeline.png
│ │ ├── start_login.png
│ │ ├── telegram_command.png
│ │ ├── telegram_command_tree.png
│ │ └── users.png
├── telegram.py
├── telegram_bus.py
├── tools.py
└── views
│ ├── telegram_command_views.xml
│ └── telegram_views.xml
├── telegram_chart
├── README.rst
├── __init__.py
├── __manifest__.py
├── doc
│ ├── changelog.rst
│ └── index.rst
├── static
│ └── description
│ │ ├── icon.png
│ │ ├── index.html
│ │ ├── pipeline-photo.jpg
│ │ └── pipeline.png
└── telegram_command.py
├── telegram_crm
├── README.rst
├── __init__.py
├── __manifest__.py
├── data
│ └── telegram_command.xml
├── doc
│ ├── changelog.rst
│ └── index.rst
└── static
│ └── description
│ ├── icon.png
│ ├── index.html
│ ├── pipeline-photo.jpg
│ └── pipeline.png
├── telegram_escpos
├── README.rst
├── __init__.py
├── __manifest__.py
├── data
│ └── telegram_command.xml
├── doc
│ ├── changelog.rst
│ └── index.rst
├── escpos.py
├── models
│ ├── __init__.py
│ └── telegram_command.py
└── static
│ └── description
│ └── icon.png
├── telegram_expense_manager
├── README.rst
├── __init__.py
├── __manifest__.py
├── data
│ ├── account.xml
│ ├── analytic.xml
│ ├── cron.xml
│ └── telegram_command.xml
├── doc
│ ├── changelog.rst
│ └── index.rst
├── models
│ ├── __init__.py
│ ├── account_account.py
│ ├── account_analytic_account.py
│ ├── api.py
│ └── schedule.py
├── security
│ └── ir.model.access.csv
├── static
│ └── description
│ │ └── icon.png
└── views
│ ├── account_account.xml
│ └── schedule.xml
├── telegram_mail
├── README.rst
├── __init__.py
├── __manifest__.py
├── data
│ ├── command.xml
│ └── ir_action_rules.xml
├── doc
│ ├── changelog.rst
│ └── index.rst
├── models
│ ├── __init__.py
│ ├── mail_channel.py
│ └── res_partner.py
├── static
│ └── description
│ │ ├── channel-add-1.png
│ │ ├── channel-add-2.png
│ │ ├── channel-create.png
│ │ ├── channel-notification.png
│ │ ├── channel-settings-1.png
│ │ ├── channel-settings-2.png
│ │ ├── icon.png
│ │ ├── index.html
│ │ └── mail.png
└── views
│ └── mail_channel.xml
├── telegram_portal
├── README.rst
├── __init__.py
├── __manifest__.py
├── data
│ ├── auth_signup_data.xml
│ └── telegram_command.xml
├── doc
│ ├── changelog.rst
│ └── index.rst
└── static
│ └── description
│ └── icon.png
├── telegram_project_issue
├── README.rst
├── __init__.py
├── __manifest__.py
├── data
│ ├── ir_cron.xml
│ └── telegram_command.xml
├── doc
│ ├── changelog.rst
│ └── index.rst
└── static
│ └── description
│ ├── icon.png
│ ├── index.html
│ └── new_issues.png
└── telegram_shop
├── README.rst
├── __init__.py
├── __manifest__.py
├── controllers
├── __init__.py
└── main.py
├── data
└── command.xml
├── static
└── description
│ ├── icon.png
│ ├── index.html
│ └── telegram_shop.gif
└── telegram_command.py
/.DINAR/config.yaml:
--------------------------------------------------------------------------------
1 | repo_readme:
2 | title: Telegram.org and Odoo.com integration
3 |
--------------------------------------------------------------------------------
/.github/workflows/DINAR-readme.yml:
--------------------------------------------------------------------------------
1 | # Copyright 2020 IT Projects Labs
2 | #
3 | # Licensed under the Apache License, Version 2.0 (the "License");
4 | # you may not use this file except in compliance with the License.
5 | # You may obtain a copy of the License at
6 | #
7 | # http://www.apache.org/licenses/LICENSE-2.0
8 | #
9 | # Unless required by applicable law or agreed to in writing, software
10 | # distributed under the License is distributed on an "AS IS" BASIS,
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | # See the License for the specific language governing permissions and
13 | # limitations under the License.
14 | name: "DINAR: update repo's readme"
15 |
16 | on:
17 | push:
18 | paths:
19 | - ".DINAR/build-date.txt"
20 | - ".DINAR/config.yaml"
21 | - ".github/workflows/DINAR-readme.yml"
22 | - "*/__manifest__.py"
23 | jobs:
24 | repo_readme:
25 | runs-on: ubuntu-latest
26 | if:
27 | "! endsWith(github.repository, '-store') && startsWith(github.repository,
28 | 'itpp-labs/')"
29 | steps:
30 | - name: Checkout Repo
31 | uses: actions/checkout@v2
32 | with:
33 | path: REPO
34 | # token is required to bypass pushing without checks/reviews
35 | token: ${{ secrets.GITHUB_TOKEN }}
36 | - name: Checkout DINAR
37 | uses: actions/checkout@v2
38 | with:
39 | path: DINAR
40 | repository: itpp-labs/DINAR-fork
41 | ref: master
42 | - uses: actions/setup-python@v1
43 | with:
44 | python-version: "3.7.x"
45 | - name: Install python tools
46 | run: |
47 | pip install plumbum pyyaml PyGithub
48 | - name: Generate readme
49 | run: |
50 | REF=${GITHUB_BASE_REF:-${GITHUB_REF}}
51 | BRANCH=${REF##*/}
52 | cd REPO
53 | python ../DINAR/workflow-files/generate-repo-readme.py ${{ secrets.GITHUB_TOKEN }} ${{ github.repository }} $BRANCH
54 | - name: Commit updates
55 | uses: stefanzweifel/git-auto-commit-action@v4
56 | with:
57 | repository: REPO
58 | commit_user_name: Mitchell Admin
59 | commit_user_email: itpp-bot@users.noreply.github.com
60 | # Commit may contain other updates, but in usual flow it's only module list
61 | commit_message: |
62 | :construction_worker_man: Update module list
63 |
64 | Sent from Github Actions (see .github/workflows/DINAR-readme.yml )
65 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 |
3 | python:
4 | - "2.7"
5 |
6 | sudo: false
7 | cache: pip
8 |
9 | addons:
10 | apt:
11 | packages:
12 | - expect-dev # provides unbuffer utility
13 | - python-lxml # because pip installation is slow
14 |
15 | env:
16 | - VERSION="10.0" LINT_CHECK="1"
17 | - VERSION="10.0" ODOO_REPO="odoo/odoo" LINT_CHECK="0"
18 | - VERSION="10.0" ODOO_REPO="OCA/OCB" LINT_CHECK="0"
19 | - VERSION="10.0" UNIT_TEST="1" LINT_CHECK="0"
20 |
21 | virtualenv:
22 | system_site_packages: true
23 |
24 | install:
25 | - pip install anybox.testing.openerp
26 | - git clone https://github.com/it-projects-llc/maintainer-quality-tools.git ${HOME}/maintainer-quality-tools
27 | - export PATH=${HOME}/maintainer-quality-tools/travis:${PATH}
28 | - travis_install_nightly
29 |
30 | script:
31 | - travis_run_tests
32 |
33 | after_success:
34 | - travis_after_tests_success
35 |
36 | notifications:
37 | email: false
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](mailto:help@itpp.dev)
2 | # [11.0] Telegram.org and Odoo.com integration
3 |
4 | :heavy_check_mark: [telegram](https://apps.odoo.com/apps/modules/11.0/telegram/)
5 |
:heavy_check_mark: [telegram_chart](https://apps.odoo.com/apps/modules/11.0/telegram_chart/)
6 |
:heavy_check_mark: [telegram_crm](https://apps.odoo.com/apps/modules/11.0/telegram_crm/)
7 |
:heavy_check_mark: [telegram_escpos](https://apps.odoo.com/apps/modules/11.0/telegram_escpos/)
8 |
:heavy_check_mark: [telegram_expense_manager](https://apps.odoo.com/apps/modules/11.0/telegram_expense_manager/)
9 |
:heavy_check_mark: [telegram_mail](https://apps.odoo.com/apps/modules/11.0/telegram_mail/)
10 |
:heavy_check_mark: [telegram_portal](https://apps.odoo.com/apps/modules/11.0/telegram_portal/)
11 |
:heavy_check_mark: [telegram_project_issue](https://apps.odoo.com/apps/modules/11.0/telegram_project_issue/)
12 |
:heavy_check_mark: [telegram_shop](https://apps.odoo.com/apps/modules/11.0/telegram_shop/)
13 |
14 | Other Addons
15 | ============
16 |
17 | | Repository | Versions |
18 | |------------|----------|
19 | | [itpp-labs/**pos-addons**](https://github.com/itpp-labs/pos-addons) | [[14.0]](https://github.com/itpp-labs/pos-addons/tree/14.0#readme) [[13.0]](https://github.com/itpp-labs/pos-addons/tree/13.0#readme) [[12.0]](https://github.com/itpp-labs/pos-addons/tree/12.0#readme) [[11.0]](https://github.com/itpp-labs/pos-addons/tree/11.0#readme) [[10.0]](https://github.com/itpp-labs/pos-addons/tree/10.0#readme) [[9.0]](https://github.com/itpp-labs/pos-addons/tree/9.0#readme) [[8.0]](https://github.com/itpp-labs/pos-addons/tree/8.0#readme) [[7.0]](https://github.com/itpp-labs/pos-addons/tree/7.0#readme) |
20 | | [itpp-labs/**mail-addons**](https://github.com/itpp-labs/mail-addons) | [[14.0]](https://github.com/itpp-labs/mail-addons/tree/14.0#readme) [[13.0]](https://github.com/itpp-labs/mail-addons/tree/13.0#readme) [[12.0]](https://github.com/itpp-labs/mail-addons/tree/12.0#readme) [[11.0]](https://github.com/itpp-labs/mail-addons/tree/11.0#readme) [[10.0]](https://github.com/itpp-labs/mail-addons/tree/10.0#readme) [[9.0]](https://github.com/itpp-labs/mail-addons/tree/9.0#readme) [[8.0]](https://github.com/itpp-labs/mail-addons/tree/8.0#readme) |
21 | | [itpp-labs/**misc-addons**](https://github.com/itpp-labs/misc-addons) | [[14.0]](https://github.com/itpp-labs/misc-addons/tree/14.0#readme) [[13.0]](https://github.com/itpp-labs/misc-addons/tree/13.0#readme) [[12.0]](https://github.com/itpp-labs/misc-addons/tree/12.0#readme) [[11.0]](https://github.com/itpp-labs/misc-addons/tree/11.0#readme) [[10.0]](https://github.com/itpp-labs/misc-addons/tree/10.0#readme) [[9.0]](https://github.com/itpp-labs/misc-addons/tree/9.0#readme) [[8.0]](https://github.com/itpp-labs/misc-addons/tree/8.0#readme) [[7.0]](https://github.com/itpp-labs/misc-addons/tree/7.0#readme) |
22 | | [itpp-labs/**sync-addons**](https://github.com/itpp-labs/sync-addons) | [[14.0]](https://github.com/itpp-labs/sync-addons/tree/14.0#readme) [[13.0]](https://github.com/itpp-labs/sync-addons/tree/13.0#readme) [[12.0]](https://github.com/itpp-labs/sync-addons/tree/12.0#readme) [[11.0]](https://github.com/itpp-labs/sync-addons/tree/11.0#readme) [[10.0]](https://github.com/itpp-labs/sync-addons/tree/10.0#readme) [[9.0]](https://github.com/itpp-labs/sync-addons/tree/9.0#readme) [[8.0]](https://github.com/itpp-labs/sync-addons/tree/8.0#readme) |
23 | | [itpp-labs/**access-addons**](https://github.com/itpp-labs/access-addons) | [[14.0]](https://github.com/itpp-labs/access-addons/tree/14.0#readme) [[13.0]](https://github.com/itpp-labs/access-addons/tree/13.0#readme) [[12.0]](https://github.com/itpp-labs/access-addons/tree/12.0#readme) [[11.0]](https://github.com/itpp-labs/access-addons/tree/11.0#readme) [[10.0]](https://github.com/itpp-labs/access-addons/tree/10.0#readme) [[9.0]](https://github.com/itpp-labs/access-addons/tree/9.0#readme) [[8.0]](https://github.com/itpp-labs/access-addons/tree/8.0#readme) |
24 | | [itpp-labs/**website-addons**](https://github.com/itpp-labs/website-addons) | [[14.0]](https://github.com/itpp-labs/website-addons/tree/14.0#readme) [[13.0]](https://github.com/itpp-labs/website-addons/tree/13.0#readme) [[12.0]](https://github.com/itpp-labs/website-addons/tree/12.0#readme) [[11.0]](https://github.com/itpp-labs/website-addons/tree/11.0#readme) [[10.0]](https://github.com/itpp-labs/website-addons/tree/10.0#readme) [[9.0]](https://github.com/itpp-labs/website-addons/tree/9.0#readme) [[8.0]](https://github.com/itpp-labs/website-addons/tree/8.0#readme) |
25 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | simplejson
2 | pyTelegramBotAPI
3 | pygal
4 | cairosvg<2.0.0
5 | tinycss
6 | cssselect
7 | emoji
8 | python-escpos
9 |
--------------------------------------------------------------------------------
/telegram/README.rst:
--------------------------------------------------------------------------------
1 | .. image:: https://itpp.dev/images/infinity-readme.png
2 | :alt: Tested and maintained by IT Projects Labs
3 | :target: https://itpp.dev
4 |
5 | ==============
6 | Telegram bot
7 | ==============
8 |
9 | The module apply monkey patch for ``PreforkServer`` in order to run new process ``WorkerTelegram``, which run threads as following:
10 |
11 | * ``WorkerTelegram`` (1 process)
12 |
13 | * ``odoo_dispatch`` (1 thread)
14 |
15 | * Instance of ``TelegramDispatch`` (evented tread to listen and notify about events from other odoo processes).
16 | * To send events the function ``registry['telegram.bus'].sendone()`` is used
17 |
18 | * ``OdooTelegramThread`` (1 thread per each database)
19 |
20 | * Listen for events from ``odoo_dispatch`` and delegate work to ``odoo_thread_pool``
21 | * ``odoo_thread_pool`` (1 pool)
22 |
23 | * Pool of threads to handle odoo events.
24 | * Handles updates of telegram parameters
25 | * Executes ``registry['telegram.command'].odoo_listener()``
26 | * Has threads number equal to ``telegram.num_odoo_threads`` parameter.
27 |
28 | * ``BotPollingThread`` (1 thread per each database with token)
29 |
30 | * Calls ``bot.polling()`` function
31 | * telebot's ``polling_thread`` (1 thread)
32 |
33 | * Listen for events from Telegram and delegate work to telebot's ``worker_pool``
34 |
35 | * telebot's ``worker_pool`` (1 pool)
36 |
37 | * Pool of thread to handle telegram events.
38 | * Executes ``registry['telegram.command'].telegram_listener()``.
39 | * Has threads number equal to ``telegram.num_telegram_threads`` parameter.
40 |
41 | Further information
42 | -------------------
43 |
44 | Odoo Apps Store: https://apps.odoo.com/apps/modules/9.0/telegram
45 |
46 |
47 | Tested on `Odoo 9.0 `_
48 |
--------------------------------------------------------------------------------
/telegram/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # local imports, that can be unused:
4 | from . import telegram
5 | from . import telegram_bus
6 | from . import controllers
7 | from . import tools
8 | from . import tools as teletools
9 |
10 | # usual imports
11 | import time
12 | import odoo
13 | from odoo.service.server import Worker
14 | from odoo.service.server import PreforkServer
15 |
16 | from odoo import SUPERUSER_ID
17 | import threading
18 | import logging
19 |
20 | _logger = logging.getLogger(__name__)
21 |
22 | try:
23 | from telebot import TeleBot, util
24 | except (ImportError, IOError) as err:
25 | # cannot import TeleBot, so create dummy class
26 | class TeleBot(object):
27 | pass
28 | _logger.debug(err)
29 |
30 |
31 | def telegram_worker():
32 | # monkey patch
33 | old_process_spawn = PreforkServer.process_spawn
34 |
35 | def process_spawn(self):
36 | old_process_spawn(self)
37 | while len(self.workers_telegram) < self.telegram_population:
38 | # only 1 telegram process we create.
39 | self.worker_spawn(WorkerTelegram, self.workers_telegram)
40 |
41 | PreforkServer.process_spawn = process_spawn
42 | old_init = PreforkServer.__init__
43 |
44 | def __init__(self, app):
45 | old_init(self, app)
46 | self.workers_telegram = {}
47 | self.telegram_population = 1
48 | PreforkServer.__init__ = __init__
49 |
50 |
51 | class WorkerTelegram(Worker):
52 | """
53 | This is main singleton process for all other telegram purposes.
54 | It creates one TelegramDispatch (events bus), one OdooTelegramThread, several TeleBotMod and BotPollingThread threads.
55 | """
56 |
57 | def __init__(self, multi):
58 | super(WorkerTelegram, self).__init__(multi)
59 | self.interval = 60*5 # 5 minutes
60 | self.threads_bundles = {} # {db_name: {odoo_thread, odoo_dispatch}}
61 | self.odoo_dispatch = False
62 | self.watchdog_timeout = self.interval*2
63 |
64 | def process_work(self):
65 | # this called by run() in while self.alive cycle
66 | if not self.odoo_dispatch:
67 | self.odoo_dispatch = telegram_bus.TelegramDispatch().start()
68 | db_names = tools.db_list()
69 | for dbname in db_names:
70 | if self.threads_bundles.get(dbname, False):
71 | continue
72 |
73 | registry = odoo.registry(dbname).check_signaling()
74 | if registry.get('telegram.bus', False):
75 | # _logger.info("telegram.bus in %s" % db_name)
76 | odoo_thread = OdooTelegramThread(self.odoo_dispatch, dbname, False)
77 | odoo_thread.start()
78 | self.threads_bundles[dbname] = {'odoo_thread': odoo_thread,
79 | 'odoo_dispatch': self.odoo_dispatch}
80 | time.sleep(self.interval)
81 |
82 |
83 | class BotPollingThread(threading.Thread):
84 | """
85 | This is father-thread for telegram bot execution-threads.
86 | When bot polling is started it at once spawns several child threads (num=telegram.num_telegram_threads).
87 | Then in __threaded_polling() it listens for events from telegram server.
88 | If it catches message from server it gives to manage this message to one of executors that calls telegram_listener().
89 | Listener do what command requires by it self or may send according command in telegram bus.
90 | For every database with token one bot and one bot_polling is created.
91 | """
92 |
93 | def __init__(self, bot):
94 | threading.Thread.__init__(self, name='BotPollingThread')
95 | self.daemon = True
96 | self.bot = bot
97 |
98 | def run(self):
99 | _logger.info("BotPollingThread started.")
100 | while True:
101 | try:
102 | self.bot.polling()
103 | except Exception as e:
104 | sleep = 15
105 | _logger.error("Error on polling. Retry in %s secs\n%s", sleep, e)
106 | time.sleep(sleep)
107 |
108 |
109 | class OdooTelegramThread(threading.Thread):
110 | """
111 | This is father-thread for odoo events execution-threads.
112 | When it started it at once spawns several execution threads.
113 | Then listens for some odoo events, pushed in telegram bus.
114 | If some event happened OdooTelegramThread find out about it by dispatch and gives to manage this event to one of executors.
115 | Executor do what needed in odoo_listener() method.
116 | Spawned threads are in odoo_thread_pool.
117 | Amount of threads = telegram.num_odoo_threads + 1
118 | """
119 |
120 | def __init__(self, dispatch, dbname, bot):
121 | threading.Thread.__init__(self, name='OdooTelegramThread')
122 | self.daemon = True
123 | self.token = False
124 | self.dispatch = dispatch
125 | self.bot = bot
126 | self.bot_thread = False
127 | self.last = 0
128 | self.dbname = dbname
129 | self.num_odoo_threads = teletools.get_int_parameter(dbname, 'telegram.num_odoo_threads')
130 |
131 | self.odoo_thread_pool = util.ThreadPool(self.num_odoo_threads)
132 |
133 | def odoo_execute(self, dbname, model, method, args, kwargs=None):
134 | kwargs = kwargs or {}
135 | db = odoo.sql_db.db_connect(dbname)
136 | odoo.registry(dbname).check_signaling()
137 | with odoo.api.Environment.manage(), db.cursor() as cr:
138 | try:
139 | _logger.debug('%s: %s %s', method, args, kwargs)
140 | env = odoo.api.Environment(cr, SUPERUSER_ID, {})
141 | getattr(env[model], method)(*args, **kwargs)
142 | except:
143 | _logger.error('Error while executing method=%s, args=%s, kwargs=%s',
144 | method, args, kwargs, exc_info=True)
145 |
146 | def run(self):
147 | _logger.info("OdooTelegramThread started with %s threads" % self.num_odoo_threads)
148 |
149 | def listener(message, dbname, odoo_thread, bot):
150 | bus_message = message['message']
151 | if bus_message['action'] == 'token_changed':
152 | _logger.debug('token_changed')
153 | self.build_new_proc_bundle(dbname, odoo_thread)
154 | elif bus_message['action'] == 'odoo_threads_changed':
155 | _logger.info('odoo_threads_changed')
156 | self.update_odoo_threads(dbname, odoo_thread)
157 | elif bus_message['action'] == 'telegram_threads_changed':
158 | _logger.info('telegram_threads_changed')
159 | self.update_telegram_threads(dbname, odoo_thread)
160 | else:
161 | self.odoo_execute(
162 | dbname,
163 | 'telegram.command',
164 | 'odoo_listener',
165 | (message, bot))
166 |
167 | token = teletools.get_parameter(self.dbname, 'telegram.token')
168 | if not self.bot and tools.token_is_valid(token):
169 | # need to launch bot manually on database start
170 | _logger.debug('on boot telegram start')
171 | self.build_new_proc_bundle(self.dbname, self)
172 |
173 | while True:
174 | # Exeptions ?
175 | # ask TelegramDispatch about some messages.
176 | msg_list = self.dispatch.poll(self.dbname, last=self.last)
177 | for msg in msg_list:
178 | if msg['id'] > self.last:
179 | self.last = msg['id']
180 | self.odoo_thread_pool.put(listener, msg, self.dbname, self, self.bot)
181 | if self.odoo_thread_pool.exception_event.wait(0):
182 | self.odoo_thread_pool.raise_exceptions()
183 |
184 | def build_new_proc_bundle(self, dbname, odoo_thread):
185 | token = teletools.get_parameter(dbname, 'telegram.token')
186 | _logger.debug(token)
187 | if teletools.token_is_valid(token):
188 | if not odoo_thread.bot:
189 | _logger.info("Database %s just obtained new token or on-boot launch.", dbname)
190 | num_telegram_threads = teletools.get_int_parameter(dbname, 'telegram.num_telegram_threads')
191 | bot = TeleBotMod(token, threaded=True, num_threads=num_telegram_threads)
192 | bot.num_telegram_threads = num_telegram_threads
193 | bot.set_update_listener(
194 | lambda messages:
195 | self.odoo_execute(
196 | dbname,
197 | 'telegram.command',
198 | 'telegram_listener_message',
199 | (messages, bot))
200 | )
201 | bot.dbname = dbname
202 | bot.add_callback_query_handler({
203 | 'function': lambda call:
204 | self.odoo_execute(
205 | dbname,
206 | 'telegram.command',
207 | 'telegram_listener_callback_query',
208 | (call, bot)
209 | ),
210 | 'filters': {}})
211 | bot_thread = BotPollingThread(bot)
212 | bot_thread.start()
213 | odoo_thread.token = token
214 | odoo_thread.bot = bot
215 | odoo_thread.bot_thread = bot_thread
216 |
217 | @staticmethod
218 | def update_odoo_threads(dbname, odoo_thread):
219 | new_num_threads = teletools.get_int_parameter(dbname, 'telegram.num_odoo_threads')
220 | diff = new_num_threads - odoo_thread.num_odoo_threads
221 | odoo_thread.num_odoo_threads += diff
222 | OdooTelegramThread._update_threads(diff, 'Odoo', odoo_thread.odoo_thread_pool)
223 |
224 | @staticmethod
225 | def update_telegram_threads(dbname, odoo_thread):
226 | new_num_threads = teletools.get_int_parameter(dbname, 'telegram.num_telegram_threads')
227 | diff = new_num_threads - odoo_thread.bot.num_telegram_threads
228 | odoo_thread.bot.num_telegram_threads += diff
229 | OdooTelegramThread._update_threads(diff, 'Telegram', odoo_thread.bot.worker_pool)
230 |
231 | @staticmethod
232 | def _update_threads(diff, proc_name, wp):
233 | if diff > 0:
234 | # add new threads
235 | wp.workers += [util.WorkerThread(wp.on_exception, wp.tasks) for _ in range(diff)]
236 | _logger.info("%s workers increased and now its amount = %s" % (proc_name, teletools.running_workers_num(wp.workers)))
237 | elif diff < 0:
238 | # decrease threads
239 | cnt = 0
240 | for i in range(len(wp.workers)):
241 | if wp.workers[i]._running:
242 | wp.workers[i].stop()
243 | _logger.info('%s worker [id=%s] stopped' % (proc_name, wp.workers[i].ident))
244 | cnt += 1
245 | if cnt >= -diff:
246 | break
247 | cnt = 0
248 | for i in range(len(wp.workers)):
249 | if not wp.workers[i]._running:
250 | _logger.info('%s worker [id=%s] joined' % (proc_name, wp.workers[i].ident))
251 | wp.workers[i].join()
252 | cnt += 1
253 | if cnt >= -diff:
254 | break
255 | _logger.info("%s workers decreased and now its amount = %s" % (proc_name, teletools.running_workers_num(wp.workers)))
256 |
257 |
258 | class TeleBotMod(TeleBot, object):
259 | """
260 | Little bit modified TeleBot. Just to control amount of children threads to be created.
261 | """
262 |
263 | def __init__(self, token, threaded=True, skip_pending=False, num_threads=2):
264 | super(TeleBotMod, self).__init__(token, threaded=False, skip_pending=skip_pending)
265 | self.worker_pool = util.ThreadPool(num_threads)
266 | self.cache = CommandCache()
267 | _logger.info("TeleBot started with %s threads" % num_threads)
268 |
269 |
270 | class CommandCache(object):
271 | """
272 | Cache structure:
273 | {
274 | : {
275 | :
276 | :
277 | }
278 | }
279 | """
280 |
281 | def __init__(self):
282 | self._vals = {}
283 |
284 | def set_value(self, command, response, tsession=None):
285 | if command.type != 'cacheable':
286 | return
287 |
288 | user_id = 0
289 | if not command.universal:
290 | user_id = tsession.user_id.id
291 |
292 | if command.id not in self._vals:
293 | self._vals[command.id] = {}
294 | self._vals[command.id][user_id] = response
295 |
296 | def get_value(self, command, tsession):
297 | user_id = 0
298 | if not command.universal:
299 | user_id = tsession.user_id.id
300 |
301 | if command.id not in self._vals:
302 | return False
303 | return self._vals[command.id].get(user_id)
304 |
--------------------------------------------------------------------------------
/telegram/__manifest__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | {
3 | "name": """Telegram Bot""",
4 | "summary": """Your best assistant!""",
5 | "category": "Telegram",
6 | "images": [],
7 | "version": "1.0.0",
8 |
9 | "author": "IT-Projects LLC",
10 | "support": "apps@it-projects.info",
11 | "website": "https://twitter.com/OdooFree",
12 | "license": "LGPL-3",
13 |
14 | "depends": [
15 | "base",
16 | "web",
17 | "base_action_rule",
18 | ],
19 | "external_dependencies": {"python": [
20 | 'telebot',
21 | 'emoji',
22 | ], "bin": []},
23 | "data": [
24 | "data/config_parameter.xml",
25 | "data/ir_action_server.xml",
26 | "data/base_action_rules.xml",
27 | "data/commands.xml",
28 | "security/ir.model.access.csv",
29 | "security/telegram_security.xml",
30 | "views/telegram_views.xml",
31 | "views/telegram_command_views.xml",
32 | ],
33 | "qweb": [
34 | ],
35 | "demo": [
36 | ],
37 |
38 | 'post_load': 'telegram_worker',
39 | "pre_init_hook": None,
40 | "post_init_hook": None,
41 | "installable": True,
42 | "auto_install": False,
43 | "application": True,
44 | }
45 |
--------------------------------------------------------------------------------
/telegram/controllers/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from . import main
3 |
--------------------------------------------------------------------------------
/telegram/controllers/main.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import logging
3 |
4 | from odoo.http import request
5 | from odoo import http
6 | from werkzeug import utils
7 |
8 | _logger = logging.getLogger(__name__)
9 |
10 |
11 | class TelegramLogin(http.Controller):
12 |
13 | @http.route('/web/login/telegram', type='http', auth='user')
14 | def do_login(self, *args, **kw):
15 | token = kw['token']
16 | command = request.env['telegram.command']\
17 | .with_context(active_test=False)\
18 | .search([('name', '=', '/login')])
19 |
20 | tsession = request.env['telegram.session'].sudo().search([('token', '=', token)])
21 | if not tsession:
22 | _logger.error('Attempt to login with wrong token')
23 | return utils.redirect('/web')
24 |
25 | tsession.write({
26 | 'user_id': request.env.uid,
27 | 'odoo_session_sid': request.session.sid,
28 | })
29 | command.send_notifications(tsession=tsession)
30 | return utils.redirect('/web')
31 |
--------------------------------------------------------------------------------
/telegram/data/base_action_rules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Telegram: Update cache for /users command
6 |
7 | 59
8 | on_create_or_write
9 |
10 |
11 |
12 | Call server_action when ir config parameter updated
13 |
14 | 63
15 | on_create_or_write
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/telegram/data/commands.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | /start
7 | Start here
8 |
9 |
10 |
11 |
12 | Welcome! Click /login and follow an instruction. Then send /help command to get list of all available commands
13 |
14 |
15 |
16 |
17 |
18 | /help
19 | Get list of available commands
20 |
21 | data = {'all_commands':''}
22 | all_commands = env['telegram.command'].search([])
23 | for command in all_commands:
24 | data['all_commands'] += str(command.description_name or command.name) + ' — ' + str(command.description)+ '\n'
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | /login
34 | Login in system to get access to commands
35 |
36 |
37 |
38 | if telegram['tsession'].user_id:
39 | data['authed'] = True
40 | else:
41 | data['authed'] = False
42 | data['auth_url'] = telegram['base_url'] + '/web/login/telegram?token=%s' % telegram['tsession'].token
43 |
44 |
45 | You already logged inPlease follow login link:
46 |
47 | data['name'] = telegram['tsession'].user_id.name
48 |
49 | Hello !
50 |
51 |
52 |
53 |
54 | /logout
55 | Bot will forget who you are
56 |
57 | You successfully logged out.
58 |
59 | telegram['tsession'].unlink()
60 |
61 |
62 |
63 |
64 |
65 | /users
66 | Get list of database users and theirs last login time
67 |
68 | cacheable
69 | True
70 |
71 |
72 |
73 | data['users'] = env['res.users'].search([('share', '=', False)])
74 |
75 |
76 |
77 | /user_ , Last login at:
78 |
79 |
80 |
81 |
82 | /user_%
83 | Get information about specific user
84 |
85 | True
86 |
87 |
88 |
89 |
90 | user_id = telegram['tmessage'].text.split('_')[1]
91 | user_id = int(user_id)
92 | data['user'] = env['res.users'].browse(user_id)
93 |
94 |
95 |
96 |
97 | /user_ , Last login at:
98 |
99 |
100 |
101 |
102 | /me
103 | Discover who you are
104 |
105 |
106 | data['name'] = telegram['tsession'].user_id.name if telegram['tsession'].user_id else None
107 |
108 |
109 |
110 | You logged in as
111 |
112 |
113 | You haven't logged in yet. Use /login to do it
114 |
115 |
116 |
117 |
118 |
119 |
120 |
--------------------------------------------------------------------------------
/telegram/data/config_parameter.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | telegram.token
6 | null
7 |
8 |
9 |
10 | telegram.num_odoo_threads
11 | 2
12 |
13 |
14 |
15 | telegram.num_telegram_threads
16 | 2
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/telegram/data/ir_action_server.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Telegram: Update cache (finds commands via "Related models" field)
6 |
7 | True
8 | ir.actions.server
9 | code
10 | model.action_update_cache()
11 |
12 |
13 | Telegram: handle subscriptions (finds commands via "Related models" field)
14 |
15 | True
16 | ir.actions.server
17 | code
18 | model.action_handle_subscriptions()
19 |
20 |
21 | Telegram: Manage threads
22 |
23 | True
24 | ir.actions.server
25 | code
26 | model.proceed_telegram_configs()
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/telegram/doc/changelog.rst:
--------------------------------------------------------------------------------
1 | `1.0.0`
2 | -------
3 |
4 | - Init version
5 |
--------------------------------------------------------------------------------
/telegram/doc/index.rst:
--------------------------------------------------------------------------------
1 | =============
2 | User manual
3 | =============
4 |
5 | Installation
6 | ============
7 |
8 | Base dependencies
9 | -----------------
10 |
11 | Install following python libs::
12 |
13 | pip install -U pip
14 | pip install -U requests
15 | pip install 'requests[security]'
16 | pip install pyTelegramBotAPI
17 | pip install emoji
18 |
19 | Charts
20 | ------
21 |
22 | Install module `Charts for Telegram Bot `__ to be able receive charts. In that case `install Pygal lib `__ too::
23 |
24 | sudo apt-get install libffi-dev
25 | sudo pip install pygal
26 | sudo pip install cairosvg tinycss cssselect
27 |
28 | Modules
29 | -------
30 |
31 | `Install `__ this module in a usual way.
32 |
33 | You can install other `Telegram modules `__ to have prepared bot commands. Otherwise you will need to create them yourself (see below).
34 |
35 | Odoo parameters
36 | ---------------
37 |
38 | * `Activate longpolling `__
39 | * Add ``telegram`` to ``--load`` parameters, e.g.::
40 |
41 | ./openerp-server --workers=2 --load telegram,web --config=/path/to/openerp-server.conf
42 |
43 | * Additionaly you can decrease log output by adding
44 |
45 | --log-handler=requests.packages.urllib3.connectionpool:ERROR
46 |
47 | It prevents log output like below::
48 |
49 | INFO telegram.local requests.packages.urllib3.connectionpool: Starting new HTTPS connection (1): api.telegram.org
50 | INFO telegram.local requests.packages.urllib3.connectionpool: Starting new HTTPS connection (1): api.telegram.org
51 | INFO telegram.local requests.packages.urllib3.connectionpool: Starting new HTTPS connection (1): api.telegram.org
52 | INFO telegram.local requests.packages.urllib3.connectionpool: Starting new HTTPS connection (1): api.telegram.org
53 | INFO telegram.local requests.packages.urllib3.connectionpool: Starting new HTTPS connection (1): api.telegram.org
54 | INFO telegram.local requests.packages.urllib3.connectionpool: Starting new HTTPS connection (1): api.telegram.org
55 | INFO telegram.local requests.packages.urllib3.connectionpool: Starting new HTTPS connection (1): api.telegram.org
56 | INFO telegram.local requests.packages.urllib3.connectionpool: Starting new HTTPS connection (1): api.telegram.org
57 | INFO telegram.local requests.packages.urllib3.connectionpool: Starting new HTTPS connection (1): api.telegram.org
58 | INFO telegram.local requests.packages.urllib3.connectionpool: Starting new HTTPS connection (1): api.telegram.org
59 |
60 | * For debugging you can increase log output for telegram module:
61 |
62 | --log-handler=openerp.addons.telegram:DEBUG
63 |
64 | Configuration
65 | =============
66 |
67 | * First of all you need to create your own telegram bot if you don't have it yet. Follow `official manual `__
68 | * `Enable technical features `__
69 | * Open ``Technical / Parameters / System Parameters`` menu.
70 |
71 | * Enter value for ``telegram.token``. This is yours telegram bot token that *bot father* provided to you.
72 | * Optional. Enter value for ``telegram.num_odoo_threads``. Number of odoo threads that may to run some tasks (calculations, reports preparation and so on) received form bot. Default value is 2.
73 | * Opntional. Enter value for ``telegram.num_telegram_threads``. If you have lots of users per database you may be prefer to have bunch of telegram threads that handles requests from telegram clients to increase response speed. Default value is 2.
74 |
75 |
76 | Usage
77 | =====
78 |
79 | * Open telegram.
80 | * Find your bot in contacts and send ``/login`` message (command)
81 | * As answer you will get link you need to follow.
82 | * Your default internet browser will be opened and you will find your self on Odoo login page.
83 | * Enter your Odoo login and password and press ``[Log in]``.
84 | * Now you are logged in.
85 | * If you already logged in Odoo on your device Odoo main page just will be opened and there is no necessity to enter your login and password.
86 |
87 | Now you can use commands to Odoo. For example ``/users`` will give you list of users. Send ``/help`` to get list of all available commands.
88 |
89 |
90 | Creating new commands
91 | =====================
92 |
93 | * Open ``Settings / Telegram / Telegram Commands`` menu
94 | * Click ``[Create]``
95 | * Follow hints to fill the form out
96 | * Click ``[Save]``
97 |
98 | If command type is not ``Normal``, then you have to make further configuration:
99 |
100 | * `Enable technical features `__
101 |
102 | For periodic reports:
103 |
104 | * Open ``Settings / Technical / Automation / Scheduled Actions``
105 | * Click ``[Create]``
106 | * At ``Technical Data`` tab specify:
107 |
108 | * **Object**: ``telegram.command``
109 | * **Method**: ``action_handle_subscriptions``
110 | * **Arguments**: ``(123,)``, where 123 is a ID of you command (can be found in url, when you open command form)
111 |
112 | * Click ``[Save]``
113 |
114 | For notifications:
115 |
116 | * Open ``Settings / Technical / Automation / Automated Actions``
117 | * Click ``[Create]``
118 |
119 | * At ``Conditions`` tab specify:
120 |
121 | * **When to Run**, e.g. ``On Creation & Update``
122 | * **Filter** if needed
123 |
124 | * At ``Actions`` tab specify:
125 |
126 | * **Server actions to run** - select ``Telegram: handle subscriptions (finds commands via "Related models" field)``
127 |
128 | * Click ``[Save]``
129 |
130 | For speeding up responses:
131 |
132 | * Open ``Settings / Technical / Automation / Automated Actions``
133 | * Click ``[Create]``
134 |
135 | * At ``Conditions`` tab specify:
136 |
137 | * **When to Run**, e.g. ``On Creation & Update``
138 | * **Filter** if needed
139 |
140 | * At ``Actions`` tab specify:
141 |
142 | * **Server actions to run** - select ``Telegram: Update cache (finds commands via "Related models" field)``
143 |
144 | * Click ``[Save]``
145 |
--------------------------------------------------------------------------------
/telegram/security/ir.model.access.csv:
--------------------------------------------------------------------------------
1 | id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
2 | access_telegram_command_global_read,access_telegram_command_global_read,model_telegram_command,,1,0,0,0
3 | access_telegram_command_admin,access_telegram_command_admin,model_telegram_command,base.group_system,1,1,1,1
4 | access_telegram_session,access_telegram_session,model_telegram_session,base.group_system,1,0,0,0
5 | access_telegram_bus,access_telegram_bus,model_telegram_bus,base.group_system,1,0,0,0
6 |
--------------------------------------------------------------------------------
/telegram/security/telegram_security.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | rule_telegram_command_groups
6 |
7 | ['|',('group_ids','in', [g.id for g in user.groups_id]), ('group_ids','=',False)]
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/telegram/static/description/base_action_rule_form.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram/static/description/base_action_rule_form.png
--------------------------------------------------------------------------------
/telegram/static/description/base_action_rule_form_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram/static/description/base_action_rule_form_2.png
--------------------------------------------------------------------------------
/telegram/static/description/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram/static/description/icon.png
--------------------------------------------------------------------------------
/telegram/static/description/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Telegram Bot
5 | Your best assistant!
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | All you need to do is create you own bot with help of the BotFather, install this application and restart odoo server with --load=telegram,web (check details on Documentation tab). Now you have your personal assistant:
15 |
16 |
17 |
18 |

19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | There are three types of commands the bot can do:
28 |
29 | - Simple Request-Response commands
30 | - Notification commands
31 | - Periodic reports
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
Request-Response commands
42 |
43 |
44 |
45 |
46 | Ask the bot what you need and he will reply immediately
47 |
48 |
49 |
50 |

51 |
52 |
53 |
54 |

55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
Notification commands
64 |
65 |
66 |
67 |
68 | The bot is ready to notify you about any events you don't want to miss.
69 |
70 |
71 |
72 |

73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
Periodic reports
82 |
83 |
84 |
85 |
86 | What about periodic reports, say, about new calls? The bot can send report on new created issues every hour.
87 |
88 |
89 |
90 |

91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
Custom commands
101 |
102 |
103 |
104 |
105 | This application provides base functionalities for telegram bot integration and few base commands. You can either create you own commands via Settings / Telegram menu or buy modules with prepared commands
106 |
107 |
108 |
109 |

110 |
111 |
112 |
113 |

114 |
115 |
116 |
117 |
118 |

119 |
120 |
121 |
122 |
123 |

124 |
125 |
126 |
127 |
128 |

129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
LIMITED OFFER!
140 |
Buy the module and we will prepare for you one custom command for FREE!
141 |
142 |
Contact us by email or fill out request form
143 |
147 |
148 |
149 |
150 |
165 | Tested on Odoo
10.0 community
166 |
167 |
182 | Tested on Odoo
10.0 enterprise
183 |
184 |
185 |
186 |
187 |
188 |
189 |
--------------------------------------------------------------------------------
/telegram/static/description/ir_cron_form.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram/static/description/ir_cron_form.png
--------------------------------------------------------------------------------
/telegram/static/description/mail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram/static/description/mail.png
--------------------------------------------------------------------------------
/telegram/static/description/new_issues.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram/static/description/new_issues.png
--------------------------------------------------------------------------------
/telegram/static/description/pipeline-photo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram/static/description/pipeline-photo.jpg
--------------------------------------------------------------------------------
/telegram/static/description/pipeline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram/static/description/pipeline.png
--------------------------------------------------------------------------------
/telegram/static/description/start_login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram/static/description/start_login.png
--------------------------------------------------------------------------------
/telegram/static/description/telegram_command.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram/static/description/telegram_command.png
--------------------------------------------------------------------------------
/telegram/static/description/telegram_command_tree.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram/static/description/telegram_command_tree.png
--------------------------------------------------------------------------------
/telegram/static/description/users.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram/static/description/users.png
--------------------------------------------------------------------------------
/telegram/telegram_bus.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import datetime
3 | import json
4 | import logging
5 | import random
6 | import select
7 | import threading
8 | import time
9 |
10 | import odoo
11 | from odoo import api, fields, models, SUPERUSER_ID
12 | from odoo.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT
13 |
14 | _logger = logging.getLogger(__name__)
15 |
16 | # longpolling timeout connection
17 | TIMEOUT = 50
18 |
19 | TELEGRAM_CHANNEL = 'telegram'
20 |
21 |
22 | def json_dump(v):
23 | return json.dumps(v, separators=(',', ':'))
24 |
25 |
26 | def hashable(key):
27 | if isinstance(key, list):
28 | key = tuple(key)
29 | return key
30 |
31 |
32 | class TelegramBus(models.Model):
33 | """
34 | This bus is to get messages from Odoo to OdooTelegramThread.
35 | Odoo sends commands to be executed to OdooTelegramThread using this bus.
36 | Such may occur for example if user got new message, or event, those like new CRM lead is created, and so on.
37 | OdooTelegramThread discovers about sent to it messages with help of TelegramDispatch, when listens according bus chanel.
38 | """
39 | _name = 'telegram.bus'
40 |
41 | create_date = fields.Datetime('Create date')
42 | channel = fields.Char('Channel')
43 | message = fields.Char('Message')
44 |
45 | @api.model
46 | def gc(self):
47 | timeout_ago = datetime.datetime.utcnow() - datetime.timedelta(seconds=TIMEOUT * 2)
48 | domain = [('create_date', '<', timeout_ago.strftime(DEFAULT_SERVER_DATETIME_FORMAT))]
49 | return self.sudo().search(domain).unlink()
50 |
51 | @api.model
52 | def sendmany(self, notifications):
53 | channels = set()
54 | for channel, message in notifications:
55 | channels.add(channel)
56 | values = {
57 | "channel": json_dump(channel),
58 | "message": json_dump(message)
59 | }
60 | self.sudo().create(values)
61 | if random.random() < 0.01:
62 | self.gc()
63 | if channels:
64 | # We have to wait until the notifications are commited in database.
65 | # When calling `NOTIFY imbus`, some concurrent threads will be
66 | # awakened and will fetch the notification in the bus table. If the
67 | # transaction is not commited yet, there will be nothing to fetch,
68 | # and the longpolling will return no notification.
69 | def notify():
70 | with odoo.sql_db.db_connect('postgres').cursor() as cr:
71 | cr.execute("notify telegram_bus, %s", (json_dump(list(channels)),))
72 | self._cr.after('commit', notify)
73 |
74 | @api.model
75 | def sendone(self, message):
76 | self.sendone_channel(TELEGRAM_CHANNEL, message)
77 |
78 | @api.model
79 | def sendone_channel(self, channel, message):
80 | self.sendmany([[channel, message]])
81 |
82 | @api.model
83 | def poll(self, channels, last=0, options=None, force_status=False):
84 | if options is None:
85 | options = {}
86 | # first poll return the notification in the 'buffer'
87 | if last == 0:
88 | timeout_ago = datetime.datetime.utcnow() - datetime.timedelta(seconds=TIMEOUT)
89 | domain = [('create_date', '>', timeout_ago.strftime(DEFAULT_SERVER_DATETIME_FORMAT))]
90 | else: # else returns the unread notifications
91 | domain = [('id', '>', last)]
92 | channels = [json_dump(c) for c in channels]
93 | domain.append(('channel', 'in', channels))
94 | notifications = self.sudo().search_read(domain)
95 | # list of notification to return
96 | result = []
97 | for notif in notifications:
98 | _logger.debug('notif: %s' % notif)
99 | result.append({
100 | 'id': notif['id'],
101 | 'channel': json.loads(notif['channel']),
102 | 'message': json.loads(notif['message']),
103 | })
104 |
105 | if result or force_status:
106 | partner_ids = options.get('bus_presence_partner_ids')
107 | if partner_ids:
108 | partners = self.env['res.partner'].browse(partner_ids)
109 | result += [{
110 | 'id': -1,
111 | 'channel': (self._cr.dbname, 'bus.presence'),
112 | 'message': {'id': r.id, 'im_status': r.im_status}} for r in partners]
113 | return result
114 |
115 |
116 | class TelegramDispatch(object):
117 | """
118 | Notifier thread. It notifies OdooTelegramThread about messages to it, sent by bus.
119 | Only one instance of TelegramDispatch for all databases.
120 | """
121 |
122 | def __init__(self):
123 | self.channels = {}
124 |
125 | def poll(self, dbname, channels=None, last=None, options=None, timeout=TIMEOUT):
126 | if not channels:
127 | channels = [TELEGRAM_CHANNEL]
128 | if options is None:
129 | options = {}
130 | if not odoo.evented:
131 | current = threading.current_thread()
132 | current._Thread__daemonic = True
133 | # rename the thread to avoid tests waiting for a longpolling
134 | current.setName("odoo.longpolling.request.%s" % current.ident)
135 | registry = odoo.registry(dbname)
136 | with registry.cursor() as cr:
137 | with odoo.api.Environment.manage():
138 | if registry.get('telegram.bus', False):
139 | env = odoo.api.Environment(cr, SUPERUSER_ID, {})
140 | notifications = env['telegram.bus'].poll(channels, last, options)
141 | else:
142 | notifications = []
143 | # or wait for future ones
144 | if not notifications:
145 | event = self.Event()
146 | for channel in channels:
147 | self.channels.setdefault(hashable(channel), []).append(event)
148 | try:
149 | event.wait(timeout=timeout)
150 | with registry.cursor() as cr:
151 | env = odoo.api.Environment(cr, SUPERUSER_ID, {})
152 | notifications = env['telegram.bus'].poll(channels, last, options, force_status=True)
153 | except Exception:
154 | # timeout
155 | pass
156 | return notifications
157 |
158 | def loop(self):
159 | """ Dispatch postgres notifications to the relevant polling threads/greenlets """
160 | _logger.info("Bus.loop listen imbus on db postgres")
161 | with odoo.sql_db.db_connect('postgres').cursor() as cr:
162 | conn = cr._cnx
163 | cr.execute("listen telegram_bus")
164 | # Commit
165 | cr.commit()
166 | while True:
167 | if select.select([conn], [], [], TIMEOUT) == ([], [], []):
168 | pass
169 | else:
170 | conn.poll()
171 | channels = []
172 | while conn.notifies:
173 | channels.extend(json.loads(conn.notifies.pop().payload))
174 | # dispatch to local threads/greenlets
175 | events = set()
176 | for channel in channels:
177 | events.update(self.channels.pop(hashable(channel), []))
178 | for event in events:
179 | event.set()
180 |
181 | def run(self):
182 | _logger.info("TelegramDispatch started")
183 | while True:
184 | try:
185 | self.loop()
186 | except Exception:
187 | _logger.exception("Bus.loop error, sleep and retry")
188 | time.sleep(TIMEOUT)
189 |
190 | def start(self):
191 | self.Event = threading.Event
192 | t = threading.Thread(name="%s.Bus" % __name__, target=self.run)
193 | t.daemon = True
194 | t.start()
195 | return self
196 |
--------------------------------------------------------------------------------
/telegram/tools.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import odoo
4 | import odoo.tools.config as config
5 | from odoo import SUPERUSER_ID
6 | import logging
7 |
8 | _logger = logging.getLogger(__name__)
9 |
10 |
11 | def get_parameter(dbname, key):
12 | db = odoo.sql_db.db_connect(dbname)
13 | odoo.registry(dbname).check_signaling()
14 | with odoo.api.Environment.manage(), db.cursor() as cr:
15 | env = odoo.api.Environment(cr, SUPERUSER_ID, {})
16 | return env['ir.config_parameter'].get_param(key)
17 |
18 |
19 | def running_workers_num(workers):
20 | res = 0
21 | for r in workers:
22 | if r._running:
23 | res += 1
24 | return res
25 |
26 |
27 | def db_list():
28 | if config['db_name']:
29 | db_names = config['db_name'].split(',')
30 | else:
31 | db_names = odoo.service.db.list_dbs(True)
32 | return db_names
33 |
34 |
35 | def get_int_parameter(dbname, key, default=1):
36 | num = get_parameter(dbname, key)
37 | try:
38 | return int(num)
39 | except:
40 | _logger.info('Wrong value of %s: %s', key, num)
41 | return default
42 |
43 |
44 | def token_is_valid(token):
45 | if token and len(token) > 10:
46 | _logger.debug('Valid token')
47 | return True
48 | _logger.debug('Invalid token')
49 | return False
50 |
--------------------------------------------------------------------------------
/telegram/views/telegram_command_views.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | telegram.command.tree
7 | telegram.command
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | telegram.command.form
22 | telegram.command
23 |
24 |
77 |
78 |
79 |
Code fields
80 |
81 | Vars that can be updated:
82 | * data - empty dictonary to be filled with any data for rendering
83 | * options - dictonary to control processing
84 | * options['notify_user_ids'] - list interger user ids to be notified in subscription commands (will affect only if user is subscribed). With empty list nobody receives notification. With unspecified value all subscribers receive notification.
85 | * options['reply_markup']
86 | * Values: types.InlineKeyboardMarkup | types.ReplyKeyboardMarkup | types.ReplyKeyboardRemove.
87 | * See reply_markup in https://core.telegram.org/bots/api#sendmessage
88 | * Provides low-level access to reply_markup. It's used implicitly in:
89 | * command.inline_keyboard_buttons, command.keyboard_buttons
90 | * options['handle_reply']
91 | * options['keep_reply_keyboard']
92 | * Set value to True to keep reply keyboard from previous repsonse. Otherwise it will be removed
93 | * Makes sense only when options['reply_markup'] is not set to new value of types.ReplyKeyboardMarkup
94 | * options['charts'] (module telegram_chart is required) - list of pygal chart objects
95 | * options['photos'] - list of dictionaries with keys
96 | * data
97 | * type = "file"|"base64". Default is "base64".
98 | * filename
99 | * options['editMessageText'] - dictonary with identifier (message_id or inline_message_id). Allows to update existed message instead of sending new one. See https://core.telegram.org/bots/api#editmessagetext.
100 | * options['handle_reply']
101 | * dictonary {'replies': {REPLY: DATA}, 'custom_reply': DATA}.
102 | * If user send some value from REPLY, then this command will be called again with telegram['callback_data'] equal to corresponded DATA and and telegram['callback_data'] equal to 'reply'.
103 | * If custom_reply is present and user send response not from 'replies', then this command is called with telegram['callback_data'] and telegram['callback_data'] equal to 'custom_reply'
104 | * context - dictonary to save json serializable data between user requests.
105 |
106 | Vars that can be used:
107 | * command - telegram.command record
108 | * command.inline_keyboard_buttons(options, buttons, row_width=None)
109 | * adds set of inline buttons to response. See https://core.telegram.org/bots/api#inlinekeyboardbutton.
110 | * Argument buttons is a list of dictonary
111 | * text=TEXT
112 | * callback_data=ANY_DATA
113 | * command.keyboard_buttons(options, buttons, row_width=None, one_time_keyboard=None, resize_keyboard=None)
114 | * adds set of reply buttons to response. See https://core.telegram.org/bots/api#keyboardbutton
115 | * one_time_keyboard and resize_keyboard make sense only for first call of the function. Also, they can be updated later via options['reply_markup'].
116 | * Argument buttons is a list of dictonary
117 | * text=TEXT
118 | * callback_data=ANY_DATA
119 | * telegram - dictonary with some input data
120 | * telegram['tsession'] - telegram.session record.
121 | * telegram['tsession'].get_odoo_session() - the same as request.session in odoo
122 | * telegram['tmessage'] - message from telegram. See https://core.telegram.org/bots/api#message
123 | * telegram['tmessage'].text - actual UTF-8 text of the message
124 | * telegram['callback_query'] - original telegram object. See https://core.telegram.org/bots/api#callbackquery
125 | * telegram['callback_query'].message.message_id - message id where inline button is clicked. Can be used in options['editMessageText']
126 |
127 | * telegram['callback_data'] - callback data. See telegram['callback_type']
128 | * telegram['callback_type']
129 | * 'reply' | 'custom_reply' - see see options['handle_reply'] and command.keyboard_buttons above
130 | * 'inline' - Is used when user click on inline button see https://core.telegram.org/bots/api#inlinekeyboardbutton. About adding inline buttons see options['reply_markup'] and command.inline_keyboard_buttons above
131 | * telegram['event'] - dictonary with keys 'active_model', 'active_id', 'active_ids'. Only for notifications
132 | * telegram['base_url'] - url of odoo database
133 |
134 | * env - odoo Environment
135 | * env.user - current user
136 | * _logger - logger
137 | * _ - translate tool
138 |
139 | Libs that can be used:
140 | * re
141 | * datetime
142 | * dateutil
143 | * time
144 | * tools (openerp.tools)
145 | * pygal (module telegram_chart is required)
146 | * types - telebot.types . See https://github.com/eternnoir/pyTelegramBotAPI/blob/master/telebot/types.py and https://core.telegram.org/bots/api#available-types
147 | * emoji - see https://pypi.python.org/pypi/emoji
148 | * emoji.emojize(':thumbsup:', use_aliases=True) - convert alias to emoji unicode. See http://www.webpagefx.com/tools/emoji-cheat-sheet/
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 | Telegram Commands
163 | ir.actions.act_window
164 | telegram.command
165 | form
166 | tree,form
167 | {'active_test': False}
168 |
169 |
170 |
171 |
172 |
173 |
174 |
--------------------------------------------------------------------------------
/telegram/views/telegram_views.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/telegram_chart/README.rst:
--------------------------------------------------------------------------------
1 | .. image:: https://itpp.dev/images/infinity-readme.png
2 | :alt: Tested and maintained by IT Projects Labs
3 | :target: https://itpp.dev
4 |
5 | =========================
6 | Charts for Telegram Bot
7 | =========================
8 |
9 | Technical module for chart support.
10 |
11 | * Adds render functions
12 | * Specifies list of external dependencies.
13 |
14 | TODO
15 | ====
16 |
17 | * settings for chart styling
18 |
19 | Questions?
20 | ==========
21 |
22 | To get an assistance on this module contact us by email :arrow_right: help@itpp.dev
23 |
24 | Contributors
25 | ============
26 | * Ivan Yelizariev
27 |
28 | Further information
29 | ===================
30 |
31 | Odoo Apps Store: https://apps.odoo.com/apps/modules/9.0/telegram_chart/
32 |
33 |
34 | Tested on `Odoo 9.0 `_
35 |
--------------------------------------------------------------------------------
/telegram_chart/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from . import telegram_command
3 |
--------------------------------------------------------------------------------
/telegram_chart/__manifest__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | {
3 | "name": """Charts for Telegram Bot""",
4 | "summary": """Technical module for charts support""",
5 | "category": "Hidden",
6 | "images": [],
7 | "version": "1.0.0",
8 |
9 | "author": "IT-Projects LLC, Ivan Yelizariev",
10 | "support": "apps@it-projects.info",
11 | "website": "https://twitter.com/OdooFree",
12 | "license": "LGPL-3",
13 |
14 | "depends": [
15 | "telegram",
16 | ],
17 | "external_dependencies": {"python": [
18 | 'pygal',
19 | 'cairosvg',
20 | 'tinycss',
21 | 'cssselect',
22 | 'lxml',
23 | ], "bin": []},
24 |
25 | "data": [
26 | ],
27 | "qweb": [
28 | ],
29 | "demo": [
30 | ],
31 |
32 | "post_load": None,
33 | "pre_init_hook": None,
34 | "post_init_hook": None,
35 | "installable": True,
36 | "auto_install": False,
37 | }
38 |
--------------------------------------------------------------------------------
/telegram_chart/doc/changelog.rst:
--------------------------------------------------------------------------------
1 | Changelog
2 | =========
3 |
4 | `1.0.0`
5 | -------
6 |
7 | - Init version
8 |
--------------------------------------------------------------------------------
/telegram_chart/doc/index.rst:
--------------------------------------------------------------------------------
1 | =========================
2 | Charts for Telegram Bot
3 | =========================
4 |
5 | Follow instruction for `Telegram Bot `__ module.
6 |
--------------------------------------------------------------------------------
/telegram_chart/static/description/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram_chart/static/description/icon.png
--------------------------------------------------------------------------------
/telegram_chart/static/description/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Charts for Telegram Bot
5 | Technical module for charts support
6 |
7 |
8 |
9 |
10 |
11 |

12 |
13 |
14 |

15 |
16 |
17 |
18 |
19 |
31 |
--------------------------------------------------------------------------------
/telegram_chart/static/description/pipeline-photo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram_chart/static/description/pipeline-photo.jpg
--------------------------------------------------------------------------------
/telegram_chart/static/description/pipeline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram_chart/static/description/pipeline.png
--------------------------------------------------------------------------------
/telegram_chart/telegram_command.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import logging
3 | import time
4 | import io
5 |
6 | try:
7 | import pygal
8 | except:
9 | pygal = None
10 |
11 | from odoo import api
12 | from odoo import models
13 |
14 | _logger = logging.getLogger(__name__)
15 |
16 |
17 | class TelegramCommand(models.Model):
18 |
19 | _inherit = "telegram.command"
20 |
21 | @api.multi
22 | def _get_globals_dict(self):
23 | res = super(TelegramCommand, self)._get_globals_dict()
24 | res['pygal'] = pygal
25 | return res
26 |
27 | @api.multi
28 | def _update_locals_dict(self, *args, **kwargs):
29 | locals_dict = super(TelegramCommand, self)._update_locals_dict(*args, **kwargs)
30 | locals_dict['options']['charts'] = []
31 | return locals_dict
32 |
33 | def _render(self, template, locals_dict, tsession):
34 | res = super(TelegramCommand, self)._render(template, locals_dict, tsession)
35 | t0 = time.time()
36 | photos = []
37 |
38 | for obj in locals_dict['options'].get('charts', []):
39 | f = io.StringIO(obj.render_to_png())
40 | f.name = 'chart.png'
41 | photos.append({'file': f})
42 |
43 | render_time = time.time() - t0
44 | _logger.debug('Render Charts in %.2fs\n locals_dict: %s', render_time, locals_dict)
45 | res['photos'] += photos
46 | return res
47 |
--------------------------------------------------------------------------------
/telegram_crm/README.rst:
--------------------------------------------------------------------------------
1 | .. image:: https://itpp.dev/images/infinity-readme.png
2 | :alt: Tested and maintained by IT Projects Labs
3 | :target: https://itpp.dev
4 |
5 | ==============
6 | Telegram CRM
7 | ==============
8 |
9 | Bot commands for CRM application.
10 |
11 | Questions?
12 | ==========
13 |
14 | To get an assistance on this module contact us by email :arrow_right: help@itpp.dev
15 |
16 | Contributors
17 | ============
18 | * Ivan Yelizariev
19 |
20 | Further information
21 | ===================
22 |
23 | Odoo Apps Store: https://apps.odoo.com/apps/modules/9.0/telegram_crm/
24 |
25 |
26 | Tested on `Odoo 9.0 `_
27 |
--------------------------------------------------------------------------------
/telegram_crm/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram_crm/__init__.py
--------------------------------------------------------------------------------
/telegram_crm/__manifest__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | {
3 | "name": """Telegram CRM""",
4 | "summary": """Bot commands for CRM application""",
5 | "category": "Telegram",
6 | "images": [],
7 | "version": "1.0.0",
8 |
9 | "author": "IT-Projects LLC, Ivan Yelizariev",
10 | "support": "apps@it-projects.info",
11 | "website": "https://twitter.com/OdooFree",
12 | "license": "LGPL-3",
13 |
14 | "depends": [
15 | "telegram_chart",
16 | "crm",
17 | ],
18 | "external_dependencies": {"python": [], "bin": []},
19 |
20 | "data": [
21 | 'data/telegram_command.xml',
22 | ],
23 | "qweb": [
24 | ],
25 | "demo": [
26 | ],
27 |
28 | "post_load": None,
29 | "pre_init_hook": None,
30 | "post_init_hook": None,
31 | "installable": True,
32 | "auto_install": False,
33 | }
34 |
--------------------------------------------------------------------------------
/telegram_crm/data/telegram_command.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | /pipeline
7 | Returns Pipeline
8 | cacheable
9 | False
10 |
11 |
12 |
13 |
14 | graph_data = command.get_graph_data()
15 | data['filters'] = graph_data['filters']
16 | bar_chart = pygal.StackedBar(title="Pipeline", x_labels=map(str, graph_data['x_labels']))
17 |
18 | for d_value, d in graph_data['data_lines'].items():
19 | bar_chart.add(d_value or 'Undefined', d['values'])
20 |
21 | options['charts'].append(bar_chart)
22 |
23 |
24 | Pipeline [x]
25 |
26 |
27 |
28 |
29 | Update telegram /pipeline
30 |
31 | on_create_or_write
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/telegram_crm/doc/changelog.rst:
--------------------------------------------------------------------------------
1 | Changelog
2 | =========
3 |
4 | `1.0.0`
5 | -------
6 |
7 | - Init version
8 |
--------------------------------------------------------------------------------
/telegram_crm/doc/index.rst:
--------------------------------------------------------------------------------
1 | ==============
2 | Telegram CRM
3 | ==============
4 |
5 | Usage
6 | =====
7 |
8 | * send to Bot::
9 |
10 | /pipeline
11 |
12 | * the Bot will send a chart for you
13 |
--------------------------------------------------------------------------------
/telegram_crm/static/description/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram_crm/static/description/icon.png
--------------------------------------------------------------------------------
/telegram_crm/static/description/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Telegram CRM
5 | Bot commands for CRM application
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
/pipeline
14 |
15 |
16 |
17 |
18 |

19 |
20 |
21 |

22 |
23 |
24 |
25 |
26 |
27 |
39 |
--------------------------------------------------------------------------------
/telegram_crm/static/description/pipeline-photo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram_crm/static/description/pipeline-photo.jpg
--------------------------------------------------------------------------------
/telegram_crm/static/description/pipeline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram_crm/static/description/pipeline.png
--------------------------------------------------------------------------------
/telegram_escpos/README.rst:
--------------------------------------------------------------------------------
1 | .. image:: https://itpp.dev/images/infinity-readme.png
2 | :alt: Tested and maintained by IT Projects Labs
3 | :target: https://itpp.dev
4 |
5 | ==============================
6 | Print text to escpos printer
7 | ==============================
8 |
9 | Adds new command /print
10 |
11 | Questions?
12 | ==========
13 |
14 | To get an assistance on this module contact us by email :arrow_right: help@itpp.dev
15 |
16 | Contributors
17 | ============
18 | * Ivan Yelizariev
19 |
20 |
21 | Further information
22 | ===================
23 |
24 | .. Odoo Apps Store: https://apps.odoo.com/apps/modules/8.0/telegram_escpos/
25 |
26 |
27 | Tested on `Odoo 8.0 `_
28 |
--------------------------------------------------------------------------------
/telegram_escpos/__init__.py:
--------------------------------------------------------------------------------
1 | from . import escpos
2 | from . import models
3 |
--------------------------------------------------------------------------------
/telegram_escpos/__manifest__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | {
3 | "name": """Print text to escpos printer""",
4 | "summary": """Use it for fun :-)""",
5 | "category": "Telegram",
6 | # "live_test_URL": "",
7 | "images": [],
8 | "version": "1.0.0",
9 | "application": False,
10 |
11 | "author": "IT-Projects LLC, Ivan Yelizariev",
12 | "support": "apps@it-projects.info",
13 | "website": "https://twitter.com/OdooFree",
14 | "license": "LGPL-3",
15 |
16 | "depends": [
17 | "telegram",
18 | ],
19 | "external_dependencies": {"python": ['escpos'], "bin": []},
20 | "data": [
21 | "data/telegram_command.xml",
22 | ],
23 | "qweb": [
24 | ],
25 | "demo": [
26 | ],
27 |
28 | "post_load": None,
29 | "pre_init_hook": None,
30 | "post_init_hook": None,
31 |
32 | "auto_install": False,
33 | "installable": True,
34 | }
35 |
--------------------------------------------------------------------------------
/telegram_escpos/data/telegram_command.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | /print ?%
7 | /print <Some Text>
8 | Print text
9 |
10 |
11 | host = env['ir.config_parameter'].get_param('telegram_escpos.host')
12 | port = env['ir.config_parameter'].get_param('telegram_escpos.port', '9100')
13 |
14 | printer = EscposNetworkPrinter(host, port=int(port))
15 | printer.set()
16 | text = telegram['tmessage'].text
17 | text = text.split(' ', 1)
18 | if len(text) == 2:
19 | text = text[1]
20 | else:
21 | text = 'No text is provided. Use follow format: /print some text'
22 |
23 | text = escpos_encode(text)
24 | printer._raw(text)
25 | printer.cut()
26 |
27 |
28 | Done!
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/telegram_escpos/doc/changelog.rst:
--------------------------------------------------------------------------------
1 | `1.0.0`
2 | -------
3 |
4 | - Init version
5 |
--------------------------------------------------------------------------------
/telegram_escpos/doc/index.rst:
--------------------------------------------------------------------------------
1 | ==============================
2 | Print text to escpos printer
3 | ==============================
4 |
5 | To use this module you need:
6 |
7 | * odoo
8 | * telegram account
9 | * network escpos printer
10 | * idea what to print
11 |
12 | Installation
13 | ============
14 |
15 | * Install `Telegram Bot `__ module
16 | * `Enable technical features `__
17 | * Open ``Technical / Parameters / System Parameters`` menu.
18 |
19 | * Set values for ``telegram_escpos.host`` and ``telegram_escpos.port``
20 |
21 | Usage
22 | =====
23 |
24 | * Send ``/print Hello World!`` to your bot
25 |
--------------------------------------------------------------------------------
/telegram_escpos/escpos.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import copy
3 |
4 | try:
5 | import jcconv
6 | except ImportError:
7 | jcconv = None
8 |
9 | try:
10 | import qrcode
11 | except ImportError:
12 | qrcode = None
13 |
14 |
15 | def encode_char(char, cur_encoding):
16 | """
17 | Encodes a single utf-8 character into a sequence of
18 | esc-pos code page change instructions and character declarations
19 | """
20 | # char_utf8 = char.encode('utf-8')
21 | encoded = ''
22 | encoding = cur_encoding # we reuse the last encoding to prevent code page switches at every character
23 | encodings = {
24 | # TODO use ordering to prevent useless switches
25 | # TODO Support other encodings not natively supported by python ( Thai, Khazakh, Kanjis )
26 | 'cp1251': TXT_ENC_WPC1251,
27 | 'cp437': TXT_ENC_PC437,
28 | 'cp850': TXT_ENC_PC850,
29 | 'cp852': TXT_ENC_PC852,
30 | 'cp857': TXT_ENC_PC857,
31 | 'cp858': TXT_ENC_PC858,
32 | 'cp860': TXT_ENC_PC860,
33 | 'cp863': TXT_ENC_PC863,
34 | 'cp865': TXT_ENC_PC865,
35 | 'cp866': TXT_ENC_PC866,
36 | 'cp862': TXT_ENC_PC862,
37 | 'cp720': TXT_ENC_PC720,
38 | 'cp936': TXT_ENC_PC936,
39 | 'iso8859_2': TXT_ENC_8859_2,
40 | 'iso8859_7': TXT_ENC_8859_7,
41 | 'iso8859_9': TXT_ENC_8859_9,
42 | 'cp1254': TXT_ENC_WPC1254,
43 | 'cp1255': TXT_ENC_WPC1255,
44 | 'cp1256': TXT_ENC_WPC1256,
45 | 'cp1257': TXT_ENC_WPC1257,
46 | 'cp1258': TXT_ENC_WPC1258,
47 | 'katakana': TXT_ENC_KATAKANA,
48 | }
49 | remaining = copy.copy(encodings)
50 |
51 | if not encoding:
52 | encoding = 'cp437'
53 |
54 | while True: # Trying all encoding until one succeeds
55 | try:
56 | if encoding == 'katakana': # Japanese characters
57 | # TODO
58 | break
59 | # if jcconv:
60 | # # try to convert japanese text to a half-katakanas
61 | # kata = jcconv.kata2half(jcconv.hira2kata(char_utf8))
62 | # if kata != char_utf8:
63 | # self.extra_chars += len(kata.decode('utf-8')) - 1
64 | # # the conversion may result in multiple characters
65 | # return encode_str(kata.decode('utf-8'))
66 | # else:
67 | # kata = char_utf8
68 | #
69 | # if kata in TXT_ENC_KATAKANA_MAP:
70 | # encoded = TXT_ENC_KATAKANA_MAP[kata]
71 | # break
72 | # else:
73 | # raise ValueError()
74 | else:
75 | encoded = char.encode(encoding)
76 | break
77 |
78 | except ValueError: # the encoding failed, select another one and retry
79 | if encoding in remaining:
80 | del remaining[encoding]
81 | if len(remaining) >= 1:
82 | encoding = remaining.items()[0][0]
83 | else:
84 | encoding = 'cp437'
85 | encoded = '\xb1' # could not encode, output error character
86 | break
87 |
88 | if encoding != cur_encoding:
89 | # if the encoding changed, remember it and prefix the character with
90 | # the esc-pos encoding change sequence
91 | cur_encoding = encoding
92 | encoded = encodings[encoding] + encoded
93 |
94 | return encoded, cur_encoding
95 |
96 |
97 | def encode_str(txt):
98 | cur_encoding = 'ascii'
99 | buffer = ''
100 | for c in txt:
101 | encoded, cur_encoding = encode_char(c, cur_encoding)
102 | buffer += encoded
103 | return buffer
104 |
105 |
106 | # """ ESC/POS Commands (Constants) """
107 |
108 | # Feed control sequences
109 | CTL_LF = '\x0a' # Print and line feed
110 | CTL_FF = '\x0c' # Form feed
111 | CTL_CR = '\x0d' # Carriage return
112 | CTL_HT = '\x09' # Horizontal tab
113 | CTL_VT = '\x0b' # Vertical tab
114 |
115 | # RT Status commands
116 | DLE_EOT_PRINTER = '\x10\x04\x01' # Transmit printer status
117 | DLE_EOT_OFFLINE = '\x10\x04\x02'
118 | DLE_EOT_ERROR = '\x10\x04\x03'
119 | DLE_EOT_PAPER = '\x10\x04\x04'
120 |
121 | # Printer hardware
122 | HW_INIT = '\x1b\x40' # Clear data in buffer and reset modes
123 | HW_SELECT = '\x1b\x3d\x01' # Printer select
124 | HW_RESET = '\x1b\x3f\x0a\x00' # Reset printer hardware
125 | # Cash Drawer
126 | CD_KICK_2 = '\x1b\x70\x00' # Sends a pulse to pin 2 []
127 | CD_KICK_5 = '\x1b\x70\x01' # Sends a pulse to pin 5 []
128 | # Paper
129 | PAPER_FULL_CUT = '\x1d\x56\x00' # Full cut paper
130 | PAPER_PART_CUT = '\x1d\x56\x01' # Partial cut paper
131 | # Text format
132 | TXT_NORMAL = '\x1b\x21\x00' # Normal text
133 | TXT_2HEIGHT = '\x1b\x21\x10' # Double height text
134 | TXT_2WIDTH = '\x1b\x21\x20' # Double width text
135 | TXT_DOUBLE = '\x1b\x21\x30' # Double height & Width
136 | TXT_UNDERL_OFF = '\x1b\x2d\x00' # Underline font OFF
137 | TXT_UNDERL_ON = '\x1b\x2d\x01' # Underline font 1-dot ON
138 | TXT_UNDERL2_ON = '\x1b\x2d\x02' # Underline font 2-dot ON
139 | TXT_BOLD_OFF = '\x1b\x45\x00' # Bold font OFF
140 | TXT_BOLD_ON = '\x1b\x45\x01' # Bold font ON
141 | TXT_FONT_A = '\x1b\x4d\x00' # Font type A
142 | TXT_FONT_B = '\x1b\x4d\x01' # Font type B
143 | TXT_ALIGN_LT = '\x1b\x61\x00' # Left justification
144 | TXT_ALIGN_CT = '\x1b\x61\x01' # Centering
145 | TXT_ALIGN_RT = '\x1b\x61\x02' # Right justification
146 | TXT_COLOR_BLACK = '\x1b\x72\x00' # Default Color
147 | TXT_COLOR_RED = '\x1b\x72\x01' # Alternative Color ( Usually Red )
148 |
149 | # Text Encoding
150 |
151 | TXT_ENC_PC437 = '\x1b\x74\x00' # PC437 USA
152 | TXT_ENC_KATAKANA = '\x1b\x74\x01' # KATAKANA (JAPAN)
153 | TXT_ENC_PC850 = '\x1b\x74\x02' # PC850 Multilingual
154 | TXT_ENC_PC860 = '\x1b\x74\x03' # PC860 Portuguese
155 | TXT_ENC_PC863 = '\x1b\x74\x04' # PC863 Canadian-French
156 | TXT_ENC_PC865 = '\x1b\x74\x05' # PC865 Nordic
157 | TXT_ENC_KANJI6 = '\x1b\x74\x06' # One-pass Kanji, Hiragana
158 | TXT_ENC_KANJI7 = '\x1b\x74\x07' # One-pass Kanji
159 | TXT_ENC_KANJI8 = '\x1b\x74\x08' # One-pass Kanji
160 | TXT_ENC_PC851 = '\x1b\x74\x0b' # PC851 Greek
161 | TXT_ENC_PC853 = '\x1b\x74\x0c' # PC853 Turkish
162 | TXT_ENC_PC857 = '\x1b\x74\x0d' # PC857 Turkish
163 | TXT_ENC_PC737 = '\x1b\x74\x0e' # PC737 Greek
164 | TXT_ENC_8859_7 = '\x1b\x74\x0f' # ISO8859-7 Greek
165 | TXT_ENC_WPC1252 = '\x1b\x74\x10' # WPC1252
166 | TXT_ENC_PC866 = '\x1b\x74\x11' # PC866 Cyrillic #2
167 | TXT_ENC_PC852 = '\x1b\x74\x12' # PC852 Latin2
168 | TXT_ENC_PC858 = '\x1b\x74\x13' # PC858 Euro
169 | TXT_ENC_KU42 = '\x1b\x74\x14' # KU42 Thai
170 | TXT_ENC_TIS11 = '\x1b\x74\x15' # TIS11 Thai
171 | TXT_ENC_TIS18 = '\x1b\x74\x1a' # TIS18 Thai
172 | TXT_ENC_TCVN3 = '\x1b\x74\x1e' # TCVN3 Vietnamese
173 | TXT_ENC_TCVN3B = '\x1b\x74\x1f' # TCVN3 Vietnamese
174 | TXT_ENC_PC720 = '\x1b\x74\x20' # PC720 Arabic
175 | TXT_ENC_WPC775 = '\x1b\x74\x21' # WPC775 Baltic Rim
176 | TXT_ENC_PC855 = '\x1b\x74\x22' # PC855 Cyrillic
177 | TXT_ENC_PC861 = '\x1b\x74\x23' # PC861 Icelandic
178 | TXT_ENC_PC862 = '\x1b\x74\x24' # PC862 Hebrew
179 | TXT_ENC_PC864 = '\x1b\x74\x25' # PC864 Arabic
180 | TXT_ENC_PC869 = '\x1b\x74\x26' # PC869 Greek
181 | TXT_ENC_PC936 = '\x1C\x21\x00' # PC936 GBK(Guobiao Kuozhan)
182 | TXT_ENC_8859_2 = '\x1b\x74\x27' # ISO8859-2 Latin2
183 | TXT_ENC_8859_9 = '\x1b\x74\x28' # ISO8859-2 Latin9
184 | TXT_ENC_PC1098 = '\x1b\x74\x29' # PC1098 Farsi
185 | TXT_ENC_PC1118 = '\x1b\x74\x2a' # PC1118 Lithuanian
186 | TXT_ENC_PC1119 = '\x1b\x74\x2b' # PC1119 Lithuanian
187 | TXT_ENC_PC1125 = '\x1b\x74\x2c' # PC1125 Ukrainian
188 | TXT_ENC_WPC1250 = '\x1b\x74\x2d' # WPC1250 Latin2
189 | TXT_ENC_WPC1251 = '\x1b\x74\x2e' # WPC1251 Cyrillic
190 | TXT_ENC_WPC1253 = '\x1b\x74\x2f' # WPC1253 Greek
191 | TXT_ENC_WPC1254 = '\x1b\x74\x30' # WPC1254 Turkish
192 | TXT_ENC_WPC1255 = '\x1b\x74\x31' # WPC1255 Hebrew
193 | TXT_ENC_WPC1256 = '\x1b\x74\x32' # WPC1256 Arabic
194 | TXT_ENC_WPC1257 = '\x1b\x74\x33' # WPC1257 Baltic Rim
195 | TXT_ENC_WPC1258 = '\x1b\x74\x34' # WPC1258 Vietnamese
196 | TXT_ENC_KZ1048 = '\x1b\x74\x35' # KZ-1048 Kazakhstan
197 |
198 | TXT_ENC_KATAKANA_MAP = {
199 | # Maps UTF-8 Katakana symbols to KATAKANA Page Codes
200 | # Half-Width Katakanas
201 | '\xef\xbd\xa1': '\xa1', # 。
202 | '\xef\xbd\xa2': '\xa2', # 「
203 | '\xef\xbd\xa3': '\xa3', # 」
204 | '\xef\xbd\xa4': '\xa4', # 、
205 | '\xef\xbd\xa5': '\xa5', # ・
206 | '\xef\xbd\xa6': '\xa6', # ヲ
207 | '\xef\xbd\xa7': '\xa7', # ァ
208 | '\xef\xbd\xa8': '\xa8', # ィ
209 | '\xef\xbd\xa9': '\xa9', # ゥ
210 | '\xef\xbd\xaa': '\xaa', # ェ
211 | '\xef\xbd\xab': '\xab', # ォ
212 | '\xef\xbd\xac': '\xac', # ャ
213 | '\xef\xbd\xad': '\xad', # ュ
214 | '\xef\xbd\xae': '\xae', # ョ
215 | '\xef\xbd\xaf': '\xaf', # ッ
216 | '\xef\xbd\xb0': '\xb0', # ー
217 | '\xef\xbd\xb1': '\xb1', # ア
218 | '\xef\xbd\xb2': '\xb2', # イ
219 | '\xef\xbd\xb3': '\xb3', # ウ
220 | '\xef\xbd\xb4': '\xb4', # エ
221 | '\xef\xbd\xb5': '\xb5', # オ
222 | '\xef\xbd\xb6': '\xb6', # カ
223 | '\xef\xbd\xb7': '\xb7', # キ
224 | '\xef\xbd\xb8': '\xb8', # ク
225 | '\xef\xbd\xb9': '\xb9', # ケ
226 | '\xef\xbd\xba': '\xba', # コ
227 | '\xef\xbd\xbb': '\xbb', # サ
228 | '\xef\xbd\xbc': '\xbc', # シ
229 | '\xef\xbd\xbd': '\xbd', # ス
230 | '\xef\xbd\xbe': '\xbe', # セ
231 | '\xef\xbd\xbf': '\xbf', # ソ
232 | '\xef\xbe\x80': '\xc0', # タ
233 | '\xef\xbe\x81': '\xc1', # チ
234 | '\xef\xbe\x82': '\xc2', # ツ
235 | '\xef\xbe\x83': '\xc3', # テ
236 | '\xef\xbe\x84': '\xc4', # ト
237 | '\xef\xbe\x85': '\xc5', # ナ
238 | '\xef\xbe\x86': '\xc6', # ニ
239 | '\xef\xbe\x87': '\xc7', # ヌ
240 | '\xef\xbe\x88': '\xc8', # ネ
241 | '\xef\xbe\x89': '\xc9', # ノ
242 | '\xef\xbe\x8a': '\xca', # ハ
243 | '\xef\xbe\x8b': '\xcb', # ヒ
244 | '\xef\xbe\x8c': '\xcc', # フ
245 | '\xef\xbe\x8d': '\xcd', # ヘ
246 | '\xef\xbe\x8e': '\xce', # ホ
247 | '\xef\xbe\x8f': '\xcf', # マ
248 | '\xef\xbe\x90': '\xd0', # ミ
249 | '\xef\xbe\x91': '\xd1', # ム
250 | '\xef\xbe\x92': '\xd2', # メ
251 | '\xef\xbe\x93': '\xd3', # モ
252 | '\xef\xbe\x94': '\xd4', # ヤ
253 | '\xef\xbe\x95': '\xd5', # ユ
254 | '\xef\xbe\x96': '\xd6', # ヨ
255 | '\xef\xbe\x97': '\xd7', # ラ
256 | '\xef\xbe\x98': '\xd8', # リ
257 | '\xef\xbe\x99': '\xd9', # ル
258 | '\xef\xbe\x9a': '\xda', # レ
259 | '\xef\xbe\x9b': '\xdb', # ロ
260 | '\xef\xbe\x9c': '\xdc', # ワ
261 | '\xef\xbe\x9d': '\xdd', # ン
262 |
263 | '\xef\xbe\x9e': '\xde', # ゙
264 | '\xef\xbe\x9f': '\xdf', # ゚
265 | }
266 |
267 | # Barcod format
268 | BARCODE_TXT_OFF = '\x1d\x48\x00' # HRI barcode chars OFF
269 | BARCODE_TXT_ABV = '\x1d\x48\x01' # HRI barcode chars above
270 | BARCODE_TXT_BLW = '\x1d\x48\x02' # HRI barcode chars below
271 | BARCODE_TXT_BTH = '\x1d\x48\x03' # HRI barcode chars both above and below
272 | BARCODE_FONT_A = '\x1d\x66\x00' # Font type A for HRI barcode chars
273 | BARCODE_FONT_B = '\x1d\x66\x01' # Font type B for HRI barcode chars
274 | BARCODE_HEIGHT = '\x1d\x68\x64' # Barcode Height [1-255]
275 | BARCODE_WIDTH = '\x1d\x77\x03' # Barcode Width [2-6]
276 | BARCODE_UPC_A = '\x1d\x6b\x00' # Barcode type UPC-A
277 | BARCODE_UPC_E = '\x1d\x6b\x01' # Barcode type UPC-E
278 | BARCODE_EAN13 = '\x1d\x6b\x02' # Barcode type EAN13
279 | BARCODE_EAN8 = '\x1d\x6b\x03' # Barcode type EAN8
280 | BARCODE_CODE39 = '\x1d\x6b\x04' # Barcode type CODE39
281 | BARCODE_ITF = '\x1d\x6b\x05' # Barcode type ITF
282 | BARCODE_NW7 = '\x1d\x6b\x06' # Barcode type NW7
283 | # Image format
284 | S_RASTER_N = '\x1d\x76\x30\x00' # Set raster image normal size
285 | S_RASTER_2W = '\x1d\x76\x30\x01' # Set raster image double width
286 | S_RASTER_2H = '\x1d\x76\x30\x02' # Set raster image double height
287 | S_RASTER_Q = '\x1d\x76\x30\x03' # Set raster image quadruple
288 |
289 |
290 | """ ESC/POS Exceptions classes """
291 |
292 |
293 | class Error(Exception):
294 | """ Base class for ESC/POS errors """
295 | def __init__(self, msg, status=None):
296 | Exception.__init__(self)
297 | self.msg = msg
298 | self.resultcode = 1
299 | if status is not None:
300 | self.resultcode = status
301 |
302 | def __str__(self):
303 | return self.msg
304 |
305 | # Result/Exit codes
306 | # 0 = success
307 | # 10 = No Barcode type defined
308 | # 20 = Barcode size values are out of range
309 | # 30 = Barcode text not supplied
310 | # 40 = Image height is too large
311 | # 50 = No string supplied to be printed
312 | # 60 = Invalid pin to send Cash Drawer pulse
313 |
314 |
315 | class BarcodeTypeError(Error):
316 | def __init__(self, msg=""):
317 | Error.__init__(self, msg)
318 | self.msg = msg
319 | self.resultcode = 10
320 |
321 | def __str__(self):
322 | return "No Barcode type is defined"
323 |
324 |
325 | class BarcodeSizeError(Error):
326 | def __init__(self, msg=""):
327 | Error.__init__(self, msg)
328 | self.msg = msg
329 | self.resultcode = 20
330 |
331 | def __str__(self):
332 | return "Barcode size is out of range"
333 |
334 |
335 | class BarcodeCodeError(Error):
336 | def __init__(self, msg=""):
337 | Error.__init__(self, msg)
338 | self.msg = msg
339 | self.resultcode = 30
340 |
341 | def __str__(self):
342 | return "Code was not supplied"
343 |
344 |
345 | class ImageSizeError(Error):
346 | def __init__(self, msg=""):
347 | Error.__init__(self, msg)
348 | self.msg = msg
349 | self.resultcode = 40
350 |
351 | def __str__(self):
352 | return "Image height is longer than 255px and can't be printed"
353 |
354 |
355 | class TextError(Error):
356 | def __init__(self, msg=""):
357 | Error.__init__(self, msg)
358 | self.msg = msg
359 | self.resultcode = 50
360 |
361 | def __str__(self):
362 | return "Text string must be supplied to the text() method"
363 |
364 |
365 | class CashDrawerError(Error):
366 | def __init__(self, msg=""):
367 | Error.__init__(self, msg)
368 | self.msg = msg
369 | self.resultcode = 60
370 |
371 | def __str__(self):
372 | return "Valid pin must be set to send pulse"
373 |
374 |
375 | class NoStatusError(Error):
376 | def __init__(self, msg=""):
377 | Error.__init__(self, msg)
378 | self.msg = msg
379 | self.resultcode = 70
380 |
381 | def __str__(self):
382 | return "Impossible to get status from the printer: " + str(self.msg)
383 |
384 |
385 | class TicketNotPrinted(Error):
386 | def __init__(self, msg=""):
387 | Error.__init__(self, msg)
388 | self.msg = msg
389 | self.resultcode = 80
390 |
391 | def __str__(self):
392 | return "A part of the ticket was not been printed: " + str(self.msg)
393 |
394 |
395 | class NoDeviceError(Error):
396 | def __init__(self, msg=""):
397 | Error.__init__(self, msg)
398 | self.msg = msg
399 | self.resultcode = 90
400 |
401 | def __str__(self):
402 | return str(self.msg)
403 |
404 |
405 | class HandleDeviceError(Error):
406 | def __init__(self, msg=""):
407 | Error.__init__(self, msg)
408 | self.msg = msg
409 | self.resultcode = 100
410 |
411 | def __str__(self):
412 | return str(self.msg)
413 |
--------------------------------------------------------------------------------
/telegram_escpos/models/__init__.py:
--------------------------------------------------------------------------------
1 | from . import telegram_command
2 |
--------------------------------------------------------------------------------
/telegram_escpos/models/telegram_command.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import logging
3 |
4 | from openerp import api
5 | from openerp import models
6 |
7 | from ..escpos import encode_str
8 |
9 | _logger = logging.getLogger(__name__)
10 |
11 | try:
12 | from escpos.printer import Network
13 | except ImportError as err:
14 | _logger.debug(err)
15 |
16 |
17 | class TelegramCommand(models.Model):
18 |
19 | _inherit = "telegram.command"
20 |
21 | @api.multi
22 | def _get_globals_dict(self):
23 | res = super(TelegramCommand, self)._get_globals_dict()
24 | res['EscposNetworkPrinter'] = Network
25 | res['escpos_encode'] = encode_str
26 | return res
27 |
--------------------------------------------------------------------------------
/telegram_escpos/static/description/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram_escpos/static/description/icon.png
--------------------------------------------------------------------------------
/telegram_expense_manager/README.rst:
--------------------------------------------------------------------------------
1 | .. image:: https://itpp.dev/images/infinity-readme.png
2 | :alt: Tested and maintained by IT Projects Labs
3 | :target: https://itpp.dev
4 |
5 | ==========================
6 | Telegram Expense Manager
7 | ==========================
8 |
9 | Expense Manager Bot.
10 |
11 | Name of deployed Bot is `@expense_manager_bot `_
12 |
13 | Questions?
14 | ==========
15 |
16 | To get an assistance on this module contact us by email :arrow_right: help@itpp.dev
17 |
18 | Contributors
19 | ============
20 | * Ivan Yelizariev
21 |
22 |
23 | Further information
24 | ===================
25 |
26 | Odoo Apps Store: https://apps.odoo.com/apps/modules/10.0/telegram_expense_manager/
27 |
28 |
29 | Tested on `Odoo 10.0 `_
30 |
--------------------------------------------------------------------------------
/telegram_expense_manager/__init__.py:
--------------------------------------------------------------------------------
1 | from . import models
2 |
--------------------------------------------------------------------------------
/telegram_expense_manager/__manifest__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | {
3 | "name": """Telegram Expense Manager""",
4 | "summary": """Expense Manager Bot""",
5 | "category": "Telegram",
6 | "images": [],
7 | "version": "1.0.0",
8 | "application": False,
9 |
10 | "author": "IT-Projects LLC, Ivan Yelizariev",
11 | "support": "apps@it-projects.info",
12 | "website": "https://twitter.com/OdooFree",
13 | "license": "LGPL-3",
14 |
15 | "depends": [
16 | "telegram",
17 | "telegram_portal",
18 | "telegram_chart",
19 | "account",
20 | "analytic",
21 | "l10n_generic_coa",
22 | ],
23 | "external_dependencies": {"python": [], "bin": []},
24 | "data": [
25 | "data/telegram_command.xml",
26 | "data/account.xml",
27 | "data/analytic.xml",
28 | "data/cron.xml",
29 | "views/schedule.xml",
30 | "views/account_account.xml",
31 | "security/ir.model.access.csv",
32 | ],
33 | "qweb": [
34 | ],
35 | "demo": [
36 | ],
37 |
38 | "post_load": None,
39 | "pre_init_hook": None,
40 | "post_init_hook": None,
41 |
42 | "auto_install": False,
43 | "installable": True,
44 | }
45 |
--------------------------------------------------------------------------------
/telegram_expense_manager/data/account.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | TELE-LIQUIDITY
6 | Liquidity account for EM Bot
7 |
8 |
9 |
10 |
11 | TELE-PAYABLE
12 | Payable account for EM Bot
13 |
14 |
15 |
16 |
17 |
18 | TELE-RECEIVABLE
19 | Receivable account for EM Bot
20 |
21 |
22 |
23 |
24 |
25 | EM Bot Payable Journal
26 | TELE-PAYABLE
27 | purchase
28 | 20
29 |
30 |
31 |
32 | EM Bot Receivable Journal
33 | TELE-RECEIVABLE
34 | sale
35 | 20
36 |
37 |
38 |
39 | EM Bot Transfer Journal
40 | TELE-TRANSFER
41 | general
42 | 20
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/telegram_expense_manager/data/analytic.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Liquidity
7 |
8 |
9 |
10 | Expenses
11 |
12 |
13 |
14 | Incomes
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/telegram_expense_manager/data/cron.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Apply scheduled transfers
7 |
8 |
9 | 1
10 | hours
11 | -1
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/telegram_expense_manager/doc/changelog.rst:
--------------------------------------------------------------------------------
1 | .. _changelog:
2 |
3 | Updates
4 | =======
5 |
6 | `1.0.0`
7 | -------
8 |
9 | - Init version
10 |
--------------------------------------------------------------------------------
/telegram_expense_manager/doc/index.rst:
--------------------------------------------------------------------------------
1 | ==========================
2 | Telegram Expense Manager
3 | ==========================
4 |
5 | Installation
6 | ============
7 |
8 | * Follow instruction for `Telegram Bot `__ module.
9 | * `Install `__ this module in a usual way
10 |
11 | Usage
12 | =====
13 |
14 | Send ``/start`` to the bot and follow his instruction.
15 |
--------------------------------------------------------------------------------
/telegram_expense_manager/models/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #from . import account_account
3 | from . import account_analytic_account
4 | from . import api
5 | from . import schedule
6 | from . import account_account
7 |
--------------------------------------------------------------------------------
/telegram_expense_manager/models/account_account.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from odoo import models, fields
3 |
4 |
5 | class Account(models.Model):
6 |
7 | _inherit = "account.account"
8 |
9 | partner_id = fields.Many2one('res.partner', 'Partner')
10 |
--------------------------------------------------------------------------------
/telegram_expense_manager/models/account_analytic_account.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from odoo import models, fields, api
3 |
4 |
5 | class AccountAnalyticAccount(models.Model):
6 |
7 | _inherit = 'account.analytic.account'
8 |
9 | partner_id = fields.Many2one(index=True)
10 | liquidity_id = fields.Many2one(
11 | 'account.analytic.account',
12 | string='Default liquidity',
13 | help='Default liquidity analytic account '
14 | 'for expense or income accounts')
15 |
16 | @api.multi
17 | def get_user_tags(self):
18 | self.ensure_one()
19 | com = self.env['telegram.command']
20 | exclude_tags = \
21 | self.env.ref(com.TAG_LIQUIDITY) + \
22 | self.env.ref(com.TAG_PAYABLE) + \
23 | self.env.ref(com.TAG_RECEIVABLE)
24 | return self.tag_ids - exclude_tags
25 |
26 | @api.multi
27 | def _compute_move_debit_credit_balance(self):
28 | """based on https://github.com/odoo/odoo/blame/10.0/addons/analytic/models/analytic_account.py
29 | account_id is replaced to analytic_account_id,
30 | account.analytic.line is replaced to account.move.line
31 | """
32 | analytic_line_obj = self.env['account.move.line']
33 | domain = [('analytic_account_id', 'in', self.mapped('id'))]
34 | if self._context.get('from_date', False):
35 | domain.append(('date', '>=', self._context['from_date']))
36 | if self._context.get('to_date', False):
37 | domain.append(('date', '<=', self._context['to_date']))
38 |
39 | account_amounts = analytic_line_obj.search_read(domain, ['analytic_account_id', 'debit', 'credit'])
40 | analytic_account_ids = set([line['analytic_account_id'][0] for line in account_amounts])
41 | data_debit = {analytic_account_id: 0.0 for analytic_account_id in analytic_account_ids}
42 | data_credit = {analytic_account_id: 0.0 for analytic_account_id in analytic_account_ids}
43 | for account_amount in account_amounts:
44 | data_debit[account_amount['analytic_account_id'][0]] += account_amount['debit']
45 | data_credit[account_amount['analytic_account_id'][0]] += account_amount['credit']
46 |
47 | for account in self:
48 | account.move_debit = data_debit.get(account.id, 0.0)
49 | account.move_credit = data_credit.get(account.id, 0.0)
50 | account.move_balance = account.move_debit - account.move_credit
51 |
52 | move_balance = fields.Monetary(compute='_compute_move_debit_credit_balance', string='Balance')
53 | move_debit = fields.Monetary(compute='_compute_move_debit_credit_balance', string='Debit')
54 | move_credit = fields.Monetary(compute='_compute_move_debit_credit_balance', string='Credit')
55 |
--------------------------------------------------------------------------------
/telegram_expense_manager/models/api.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from odoo import models, api, fields
3 | from odoo.exceptions import AccessError
4 | from odoo.tools.translate import _
5 | from odoo.tools.safe_eval import safe_eval
6 |
7 | TYPE_LIQUIDITY = 'account.data_account_type_liquidity'
8 | TYPE_PAYABLE = 'account.data_account_type_payable'
9 | TYPE_RECEIVABLE = 'account.data_account_type_receivable'
10 |
11 | TAG_LIQUIDITY = 'telegram_expense_manager.analytic_tag_liquidity'
12 | TAG_PAYABLE = 'telegram_expense_manager.analytic_tag_payable'
13 | TAG_RECEIVABLE = 'telegram_expense_manager.analytic_tag_receivable'
14 |
15 | ACCOUNT_LIQUIDITY = 'telegram_expense_manager.account_liquidity'
16 | ACCOUNT_PAYABLE = 'telegram_expense_manager.account_payable'
17 | ACCOUNT_RECEIVABLE = 'telegram_expense_manager.account_receivable'
18 |
19 | JOURNAL_TRANSFER = 'telegram_expense_manager.journal_transfer'
20 | JOURNAL_PAYABLE = 'telegram_expense_manager.journal_payable'
21 | JOURNAL_RECEIVABLE = 'telegram_expense_manager.journal_receivable'
22 |
23 | TAG2TYPE = {
24 | TAG_LIQUIDITY: TYPE_LIQUIDITY,
25 | TAG_PAYABLE: TYPE_PAYABLE,
26 | TAG_RECEIVABLE: TYPE_RECEIVABLE,
27 | }
28 |
29 |
30 | ASK_AMOUNT = 'ask_amount'
31 | ASK_NOTE = 'ask_note'
32 | ASK_ANALYTIC_TAG = 'ask_analytic_tag'
33 | ASK_ANALYTIC = 'ask_analytic'
34 | ASK_PERIODICITY_TYPE = 'ask_periodicity_type'
35 | ASK_PERIODICITY_AMOUNT = 'ask_periodicity_AMOUNT'
36 | ASK_NOTIFY_ON_TRANSFER = 'ask_notify_on_transfer'
37 |
38 |
39 | PERIODICITY_OPTIONS = {
40 | 'day': {
41 | 1: _("Every day"),
42 | 2: _("Every 2 days"),
43 | 3: _("Every 3 days"),
44 | 4: _("Every 4 days"),
45 | 5: _("Every 5 days"),
46 | 6: _("Every 6 days"),
47 | },
48 | 'week': {
49 | 1: _("Every week"),
50 | 2: _("Every 2 weeks"),
51 | 3: _("Every 3 weeks"),
52 | },
53 | 'month': {
54 | 1: _("Every month"),
55 | 2: _("Every 2 months"),
56 | 3: _("Every 3 months"),
57 | 6: _("Every 6 months"),
58 | 12: _("Every 12 months"),
59 | },
60 | }
61 |
62 |
63 | class TelegramCommand(models.Model):
64 |
65 | _inherit = 'telegram.command'
66 |
67 | TAG_LIQUIDITY = TAG_LIQUIDITY
68 | TAG_PAYABLE = TAG_PAYABLE
69 | TAG_RECEIVABLE = TAG_RECEIVABLE
70 |
71 | JOURNAL_TRANSFER = JOURNAL_TRANSFER
72 | JOURNAL_PAYABLE = JOURNAL_PAYABLE
73 | JOURNAL_RECEIVABLE = JOURNAL_RECEIVABLE
74 |
75 |
76 | class Partner(models.Model):
77 |
78 | _inherit = 'res.partner'
79 |
80 | @api.multi
81 | def em_browse_record(self, record_id):
82 | record = self.env['account.move'].sudo().browse(record_id)
83 | self.em_check_access(record)
84 | return record
85 |
86 | def em_browse_schedule(self, record_id):
87 | record = self.env['account.schedule'].sudo().browse(record_id)
88 | self.em_check_access(record)
89 | return record
90 |
91 | @api.model
92 | def em_browse_line(self, line_id):
93 | line = self.env['account.move.line'].sudo().browse(line_id)
94 | record = line.move_id
95 | self.em_check_access(record)
96 | return line
97 |
98 | @api.multi
99 | def em_browse_analytic(self, analytic_id):
100 | self.ensure_one()
101 | analytic = self.env['account.analytic.account'].sudo().browse(analytic_id)
102 | if analytic.partner_id != self:
103 | raise AccessError(_("You don't have access to this analytic"))
104 | return analytic
105 |
106 | @api.multi
107 | def em_ask_analytic(self, options, command, record, tag_ref=None, tag_id=None, is_from=None, is_to=None):
108 | if not tag_ref:
109 | tag_ref = self._tag2ref()[tag_id]
110 |
111 | data = {
112 | 'action': ASK_ANALYTIC,
113 | 'tag_ref': tag_ref,
114 | 'record_id': record.id,
115 | }
116 | if is_from or is_to:
117 | data['transfer'] = 'from' if is_from else 'to'
118 |
119 | buttons = [
120 | {'text': an.name,
121 | 'callback_data': dict(
122 | data.items() +
123 | [('analytic_id', an.id)]
124 | )
125 | } for an in self._em_all_analytics(tag_ref=tag_ref, tag_id=tag_id)
126 | ]
127 | command.keyboard_buttons(options, buttons, row_width=1)
128 | options['handle_reply']['custom_reply'] = data
129 | return buttons
130 |
131 | def em_ask_amount(self, options, command, record):
132 | self._em_ask(options, command, record, ASK_AMOUNT)
133 |
134 | def em_ask_note(self, options, command, record):
135 | self._em_ask(options, command, record, ASK_NOTE)
136 |
137 | def em_ask_analytic_tag(self, options, command, record, is_from=None, is_to=None):
138 | data = {
139 | 'action': ASK_ANALYTIC_TAG,
140 | 'record_id': record.id,
141 | }
142 | if is_from or is_to:
143 | data['transfer'] = 'from' if is_from else 'to'
144 |
145 | TAG2STRING = {
146 | TAG_LIQUIDITY: _("Account"),
147 | TAG_PAYABLE: _("Expense"),
148 | TAG_RECEIVABLE: _("Income"),
149 | }
150 | if is_from:
151 | del TAG2STRING[TAG_PAYABLE]
152 | if is_to:
153 | del TAG2STRING[TAG_RECEIVABLE]
154 |
155 | buttons = [
156 | {'text': name,
157 | 'callback_data': dict(
158 | data.items() +
159 | [('tag_ref', tag_ref)]
160 | )
161 | } for tag_ref, name in TAG2STRING.items()
162 | ]
163 | command.keyboard_buttons(options, buttons, row_width=1)
164 | options['handle_reply']['custom_reply'] = data
165 | return buttons
166 |
167 | def em_ask_periodicity_type(self, options, command, record):
168 | data = {
169 | 'action': ASK_PERIODICITY_TYPE,
170 | 'record_id': record.id,
171 | }
172 |
173 | buttons = [
174 | {'text': name,
175 | 'callback_data': dict(
176 | data.items() +
177 | [('periodicity_type', code)]
178 | )
179 | } for code, name in self.env['account.schedule']._fields['periodicity_type'].selection
180 | ]
181 | command.keyboard_buttons(options, buttons, row_width=1)
182 | return buttons
183 |
184 | def em_ask_periodicity_amount(self, options, command, record):
185 | data = {
186 | 'action': ASK_PERIODICITY_AMOUNT,
187 | 'record_id': record.id,
188 | }
189 |
190 | buttons = [
191 | {'text': name,
192 | 'callback_data': dict(
193 | data.items() +
194 | [('periodicity_amount', value)]
195 | )
196 | } for value, name in PERIODICITY_OPTIONS[record.periodicity_type].items()
197 | ]
198 | command.keyboard_buttons(options, buttons, row_width=1)
199 | options['handle_reply']['custom_reply'] = data
200 | return buttons
201 |
202 | def em_ask_notify_on_transfer(self, options, command, schedule):
203 | data = {
204 | 'action': ASK_NOTIFY_ON_TRANSFER,
205 | 'record_id': schedule.id,
206 | }
207 |
208 | buttons = [
209 | {'text': name,
210 | 'callback_data': dict(
211 | data.items() +
212 | [('notify', code)]
213 | )
214 | } for code, name in self.env['account.schedule']._fields['notify'].selection
215 | ]
216 | command.keyboard_buttons(options, buttons, row_width=1)
217 | return buttons
218 |
219 | def _em_ask(self, options, command, record, action):
220 | data = {
221 | 'action': action,
222 | 'record_id': record.id if record else 0,
223 | }
224 | options['handle_reply'] = {
225 | 'replies': {},
226 | 'custom_reply': data,
227 | }
228 |
229 | @api.multi
230 | def em_handle_callback_data(self, callback_data, raw_text, add_record=None):
231 | record = self.em_browse_record(callback_data.get('record_id')) \
232 | if callback_data.get('record_id') else None
233 | error = None
234 |
235 | if callback_data.get('action') == ASK_AMOUNT:
236 | if not record:
237 | record = add_record('', raw_text)
238 | else:
239 | record.em_update_amount(raw_text)
240 | elif callback_data.get('action') == ASK_NOTE:
241 | record.em_update_note(raw_text)
242 | elif callback_data.get('action') == ASK_ANALYTIC:
243 | tag = callback_data.get('tag_ref')
244 | if callback_data.get('analytic_id'):
245 | analytic_liquidity = self.em_browse_analytic(callback_data.get('analytic_id'))
246 | else:
247 | analytic_liquidity = self._em_create_analytic(raw_text, tag)
248 | record._em_update_analytic(analytic_liquidity, TAG2TYPE[tag], callback_data.get('transfer'))
249 | return record, error
250 |
251 | @api.multi
252 | def em_handle_callback_data_schedule(self, callback_data, raw_text):
253 | record = self.em_browse_schedule(callback_data.get('record_id')) \
254 | if callback_data.get('record_id') else None
255 | error = None
256 |
257 | if callback_data.get('action') == ASK_AMOUNT:
258 | if not record:
259 | user_id = self.env.user.id
260 | record = self.env['account.schedule'].sudo().create({'user_id': user_id})
261 | record.amount = raw_text
262 | elif callback_data.get('action') == ASK_NOTE:
263 | record.name = raw_text
264 | elif callback_data.get('action') == ASK_ANALYTIC_TAG:
265 | tag = callback_data.get('tag_ref')
266 | tag = self.env.ref(tag)
267 | if callback_data.get('transfer') == 'from':
268 | record.from_tag_id = tag
269 | else:
270 | record.to_tag_id = tag
271 | elif callback_data.get('action') == ASK_ANALYTIC:
272 | tag = callback_data.get('tag_ref')
273 | if callback_data.get('analytic_id'):
274 | analytic = self.em_browse_analytic(callback_data.get('analytic_id'))
275 | else:
276 | analytic = self._em_create_analytic(raw_text, tag)
277 | if callback_data.get('transfer') == 'from':
278 | record.from_analytic_id = analytic
279 | else:
280 | record.to_analytic_id = analytic
281 | elif callback_data.get('action') == ASK_PERIODICITY_TYPE:
282 | record.periodicity_type = callback_data.get('periodicity_type')
283 | elif callback_data.get('action') == ASK_PERIODICITY_AMOUNT:
284 | record.periodicity_amount = callback_data.get('periodicity_amount')
285 | elif callback_data.get('action') == ASK_NOTIFY_ON_TRANSFER:
286 | record.notify = callback_data.get('notify')
287 | return record, error
288 |
289 | @api.multi
290 | def em_check_access(self, record, raise_on_error=True):
291 | self.ensure_one()
292 | if not record.partner_id or record.partner_id != self:
293 | if raise_on_error:
294 | raise AccessError(_("You don't have access to this record"))
295 | return False
296 | return True
297 |
298 | @api.multi
299 | def em_all_analytics_liquidity(self, tag):
300 | return self._em_all_analytics(TAG_LIQUIDITY)
301 |
302 | @api.multi
303 | def em_all_analytics_payable(self, tag):
304 | return self._em_all_analytics(TAG_PAYABLE)
305 |
306 | @api.multi
307 | def _em_all_analytics(self, tag_ref, tag_id=None, count=False):
308 | self.ensure_one()
309 | if tag_ref:
310 | tag_id = self.env.ref(tag_ref).id
311 | if not tag_id:
312 | return []
313 | domain = [('partner_id', '=', self.id)]
314 | domain += [('tag_ids', '=', tag_id)]
315 | return self.env['account.analytic.account'].search(domain, count=count)
316 |
317 | @api.multi
318 | def em_default_analytic_payable(self, text):
319 | return self._em_guess_analytic(text, ACCOUNT_PAYABLE)
320 |
321 | @api.multi
322 | def em_default_analytic_liquidity(self, text):
323 | analytic = self._em_guess_analytic(text, ACCOUNT_LIQUIDITY)
324 | if analytic:
325 | return analytic
326 | count = self._em_all_analytics(TAG_LIQUIDITY, count=True)
327 | if not count:
328 | analytic = self.em_create_analytic_liquidity(_('General account'))
329 | return analytic
330 | elif count == 1:
331 | return self._em_all_analytics(TAG_LIQUIDITY)
332 | else:
333 | # More than one analytics. Let user to choose himself
334 | return self.env['account.analytic.account']
335 |
336 | @api.multi
337 | def _em_guess_analytic(self, text, account_ref):
338 | account = self.env.ref(account_ref)
339 | line = self.env['account.move.line'].search([
340 | ('partner_id', '=', self.id),
341 | ('name', '=ilike', text),
342 | ('account_id', '=', account.id)
343 | ], order='id DESC', limit=1)
344 | if not line:
345 | return self.env['account.analytic.account']
346 | return line.analytic_account_id
347 |
348 | @api.multi
349 | def em_create_analytic_liquidity(self, name):
350 | return self._em_create_analytic(name, TAG_LIQUIDITY)
351 |
352 | @api.multi
353 | def em_create_analytic_payable(self, name):
354 | return self._em_create_analytic(name, TAG_PAYABLE)
355 |
356 | @api.multi
357 | def _em_create_analytic(self, name, tag):
358 | tag = self.env.ref(tag)
359 | analytic = self.env['account.analytic.account'].sudo().create({
360 | 'name': name,
361 | 'partner_id': self.id,
362 | 'tag_ids': [(4, tag.id, None)],
363 | })
364 | return analytic
365 |
366 | @api.multi
367 | def em_add_expense_record(self, text, amount, currency=None):
368 | amount = safe_eval(amount)
369 | account_liquidity = self.env.ref(ACCOUNT_LIQUIDITY)
370 | account_payable = self.env.ref(ACCOUNT_PAYABLE)
371 | analytic_payable = self.em_default_analytic_payable(text)
372 | analytic_liquidity = analytic_payable.liquidity_id \
373 | or self.em_default_analytic_liquidity(text)
374 |
375 | from_data = {
376 | 'account_id': account_liquidity.id,
377 | 'analytic_account_id': analytic_liquidity.id,
378 | }
379 | to_data = {
380 | 'account_id': account_payable.id,
381 | 'analytic_account_id': analytic_payable.id,
382 | }
383 | return self._em_add_record(text, amount, currency,
384 | JOURNAL_PAYABLE, from_data, to_data)
385 |
386 | @api.multi
387 | def em_add_income_record(self, text, amount, currency=None):
388 | account_liquidity = self.env.ref(ACCOUNT_LIQUIDITY)
389 | account_receivable = self.env.ref(ACCOUNT_RECEIVABLE)
390 |
391 | from_data = {
392 | 'account_id': account_receivable.id,
393 | }
394 | to_data = {
395 | 'account_id': account_liquidity.id,
396 | }
397 | return self._em_add_record(text, amount, currency,
398 | JOURNAL_RECEIVABLE, from_data, to_data)
399 |
400 | @api.multi
401 | def em_add_transfer_record(self, text, amount, currency=None):
402 | account_liquidity = self.env.ref(ACCOUNT_LIQUIDITY)
403 |
404 | from_data = {
405 | 'account_id': account_liquidity.id,
406 | }
407 | to_data = {
408 | 'account_id': account_liquidity.id,
409 | }
410 | return self._em_add_record(text, amount, currency,
411 | JOURNAL_TRANSFER, from_data, to_data)
412 |
413 | def _tag2ref(self):
414 | return {
415 | self.env.ref(self.env['telegram.command'].TAG_RECEIVABLE).id: TAG_RECEIVABLE,
416 | self.env.ref(self.env['telegram.command'].TAG_LIQUIDITY).id: TAG_LIQUIDITY,
417 | self.env.ref(self.env['telegram.command'].TAG_PAYABLE).id: TAG_PAYABLE,
418 | }
419 |
420 | @api.multi
421 | def em_add_record_from_schedule(self, schedule):
422 | if not all([
423 | schedule.from_tag_id,
424 | schedule.to_tag_id,
425 | schedule.from_analytic_id,
426 | schedule.to_analytic_id,
427 | schedule.amount,
428 | ]):
429 | return None
430 | from_ref = self._tag2ref()[schedule.from_tag_id.id]
431 | to_ref = self._tag2ref()[schedule.to_tag_id.id]
432 | if from_ref == TAG_RECEIVABLE:
433 | account_from = self.env.ref(ACCOUNT_RECEIVABLE)
434 | account_to = self.env.ref(ACCOUNT_LIQUIDITY)
435 | journal = JOURNAL_RECEIVABLE
436 | elif to_ref == TAG_PAYABLE:
437 | account_from = self.env.ref(ACCOUNT_LIQUIDITY)
438 | account_to = self.env.ref(ACCOUNT_PAYABLE)
439 | journal = JOURNAL_PAYABLE
440 | else:
441 | account_from = self.env.ref(ACCOUNT_LIQUIDITY)
442 | account_to = self.env.ref(ACCOUNT_LIQUIDITY)
443 | journal = JOURNAL_TRANSFER
444 |
445 | from_data = {
446 | 'account_id': account_from.id,
447 | 'analytic_account_id': schedule.from_analytic_id.id,
448 | }
449 | to_data = {
450 | 'account_id': account_to.id,
451 | 'analytic_account_id': schedule.to_analytic_id.id,
452 | }
453 |
454 | text = schedule.name or _('undefined')
455 | currency = None
456 | amount = schedule.amount
457 |
458 | record = self._em_add_record(text, amount, currency,
459 | journal, from_data, to_data)
460 | record.schedule_id = schedule
461 | return record
462 |
463 | def _em_add_record(self,
464 | text, amount, currency,
465 | journal_ref, from_data, to_data):
466 |
467 | journal = self.env.ref(journal_ref)
468 |
469 | common = {
470 | 'partner_id': self.id,
471 | 'name': text or 'unknown',
472 | }
473 | if isinstance(amount, basestring):
474 | amount = float(amount.replace(',', '.'))
475 |
476 | # move from source (e.g. wallet)
477 | credit = common.copy()
478 | credit.update(from_data)
479 | credit.update({
480 | 'credit': amount,
481 | })
482 | # move to target (e.g. cashier)
483 | debit = common.copy()
484 | debit.update(to_data)
485 | debit.update({
486 | 'debit': amount,
487 | })
488 | record = self.env['account.move'].create({
489 | 'narration': text,
490 | 'journal_id': journal.id,
491 | 'line_ids': [
492 | (0, 0, debit),
493 | (0, 0, credit),
494 | ]
495 | })
496 | return record
497 |
498 |
499 | class AccountMove(models.Model):
500 | """Class for ``record``"""
501 | _inherit = 'account.move'
502 |
503 | schedule_id = fields.Many2one('account.schedule', help='Schedule which created this record')
504 |
505 | @api.multi
506 | def em_update_amount(self, amount):
507 | self.ensure_one()
508 | update_lines = []
509 | for line in self.line_ids:
510 | if line.is_from:
511 | update_lines.append((1, line.id, {'credit': amount}))
512 | elif line.is_to:
513 | update_lines.append((1, line.id, {'debit': amount}))
514 | self.write({'line_ids': update_lines})
515 |
516 | @api.multi
517 | def em_update_note(self, text):
518 | self.narration = text
519 | self.line_ids.write({'name': text})
520 |
521 | @api.multi
522 | def em_update_analytic_liquidity(self, analytic_liquidity):
523 | return self._em_update_analytic(
524 | analytic_liquidity, TYPE_LIQUIDITY)
525 |
526 | @api.multi
527 | def em_update_analytic_payable(self, analytic_payable):
528 | return self._em_update_analytic(
529 | analytic_payable, TYPE_PAYABLE)
530 |
531 | @api.multi
532 | def _em_update_analytic(self, new_analytic, user_type_ref=None, transfer=None):
533 | user_type = self.env.ref(user_type_ref)
534 | for line in self.line_ids:
535 | update = False
536 | if transfer:
537 | if line.is_from and transfer == 'from':
538 | update = True
539 |
540 | if line.is_to and transfer == 'to':
541 | update = True
542 |
543 | elif line.account_id.user_type_id == user_type:
544 | update = True
545 |
546 | if update:
547 | line.analytic_account_id = new_analytic
548 | return True
549 | return False
550 |
551 | @api.multi
552 | def em_get_analytic_liquidity(self):
553 | return self._em_get_analytic(TYPE_LIQUIDITY)
554 |
555 | @api.multi
556 | def em_get_analytic_payable(self):
557 | return self._em_get_analytic(TYPE_PAYABLE)
558 |
559 | def em_get_analytic_receivable(self):
560 | return self._em_get_analytic(TYPE_RECEIVABLE)
561 |
562 | @api.multi
563 | def _em_get_analytic(self, user_type_ref):
564 | user_type = self.env.ref(user_type_ref)
565 | for line in self.line_ids:
566 | if line.account_id.user_type_id == user_type:
567 | return line.analytic_account_id
568 | return False
569 |
570 | @api.multi
571 | def em_get_analytic_from(self):
572 | for line in self.line_ids:
573 | if line.is_from:
574 | return line.analytic_account_id
575 | return False
576 |
577 | @api.multi
578 | def em_get_analytic_to(self):
579 | for line in self.line_ids:
580 | if line.is_to:
581 | return line.analytic_account_id
582 | return False
583 |
584 | @api.multi
585 | def em_lines(self):
586 | res = {
587 | 'from': {},
588 | 'to': {}
589 | }
590 | for line in self.line_ids:
591 | key = 'from' if line.is_from else 'to'
592 | res[key] = {
593 | 'analytic_id': line.analytic_account_id.id,
594 | # TODO: rename to analytic_name or put here analytic record instead of just name
595 | 'analytic': line.analytic_account_id.name,
596 | 'id': line.id,
597 | }
598 | return res
599 |
600 |
601 | class AccountMoveLine(models.Model):
602 |
603 | _inherit = 'account.move.line'
604 |
605 | is_from = fields.Boolean(compute='_compute_em_type')
606 | is_to = fields.Boolean(compute='_compute_em_type')
607 |
608 | def _compute_em_type(self):
609 | for r in self:
610 | r.is_from = r.credit or r.account_id == self.env.ref(ACCOUNT_RECEIVABLE)
611 | r.is_to = r.debit or r.account_id == self.env.ref(ACCOUNT_PAYABLE)
612 |
613 | @api.multi
614 | def _em_analytic_tag(self):
615 | self.ensure_one()
616 | return {
617 | self.env.ref(ACCOUNT_RECEIVABLE).id: TAG_RECEIVABLE,
618 | self.env.ref(ACCOUNT_PAYABLE).id: TAG_PAYABLE,
619 | self.env.ref(ACCOUNT_LIQUIDITY).id: TAG_LIQUIDITY,
620 | }.get(self.account_id.id)
621 |
622 | @api.multi
623 | def em_all_analytics(self):
624 | self.ensure_one()
625 | partner = self.partner_id
626 | assert partner, _("Record line doesn't have partner value")
627 | return partner._em_all_analytics(self._em_analytic_tag())
628 |
629 | def em_create_analytic(self, name):
630 | self.ensure_one()
631 | partner = self.partner_id
632 | assert partner, _("Record line doesn't have partner value")
633 | tag = self._em_analytic_tag()
634 | return partner._em_create_analytic(name, tag)
635 |
636 | def em_update_analytic(self, analytic):
637 | self.analytic_account_id = analytic
638 |
--------------------------------------------------------------------------------
/telegram_expense_manager/models/schedule.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import logging
3 |
4 | from datetime import timedelta
5 |
6 | from odoo import models, fields, api
7 |
8 | _logger = logging.getLogger(__name__)
9 |
10 |
11 | class Schedule(models.Model):
12 |
13 | _name = "account.schedule"
14 |
15 | def _type_tags(self):
16 | return [
17 | self.env.ref(self.env['telegram.command'].TAG_RECEIVABLE).id,
18 | self.env.ref(self.env['telegram.command'].TAG_LIQUIDITY).id,
19 | self.env.ref(self.env['telegram.command'].TAG_PAYABLE).id,
20 | ]
21 |
22 | name = fields.Char('Name')
23 | active = fields.Boolean('Active', default=True)
24 |
25 | user_id = fields.Many2one(
26 | 'res.users',
27 | string='User',
28 | required=True,
29 | default=lambda self: self.env.user.id,
30 | )
31 | partner_id = fields.Many2one(
32 | 'res.partner',
33 | related='user_id.partner_id'
34 | )
35 |
36 | from_tag_id = fields.Many2one(
37 | 'account.analytic.tag',
38 | string='Type',
39 | domain=lambda self: [('id', 'in',
40 | [
41 | self.env.ref(self.env['telegram.command'].TAG_RECEIVABLE).id,
42 | self.env.ref(self.env['telegram.command'].TAG_LIQUIDITY).id,
43 | ])]
44 | )
45 |
46 | from_analytic_id = fields.Many2one(
47 | 'account.analytic.account',
48 | string='Analytic',
49 | domain="""[
50 | ('partner_id', '=', partner_id),
51 | ('tag_ids', '=', from_tag_id),
52 | ]"""
53 | )
54 |
55 | to_tag_id = fields.Many2one(
56 | 'account.analytic.tag',
57 | string='Type',
58 | domain=lambda self: [('id', 'in',
59 | [
60 | self.env.ref(self.env['telegram.command'].TAG_LIQUIDITY).id,
61 | self.env.ref(self.env['telegram.command'].TAG_PAYABLE).id,
62 | ])]
63 | )
64 |
65 | to_analytic_id = fields.Many2one(
66 | 'account.analytic.account',
67 | string='Analytic',
68 | domain="""[
69 | ('partner_id', '=', partner_id),
70 | ('tag_ids', '=', to_tag_id),
71 | ]"""
72 | )
73 |
74 | periodicity_type = fields.Selection([
75 | ('day', 'Daily'),
76 | ('week', 'Weekly'),
77 | ('month', 'Monthly'),
78 | ], string='Periodicity Type')
79 |
80 | periodicity_amount = fields.Integer('Periodicity Amount')
81 | date = fields.Datetime('Start point',
82 | default=fields.Datetime.now,
83 | help="""Start point to compute next date.
84 | Equal to creation date, manually set value or last action date""")
85 | next_date = fields.Datetime(
86 | 'Next Action',
87 | help="Date of next activation",
88 | compute="_compute_next_date",
89 | store=True,
90 | )
91 |
92 | currency_id = fields.Many2one(
93 | 'res.currency',
94 | string='Account Currency',
95 | default=lambda self: self.env.user.company_id.currency_id,
96 | )
97 | amount = fields.Monetary('Amount', help='Amount of money to be transfered')
98 | notify = fields.Selection([
99 | ('no', "Don't notify"),
100 | ('instantly', "Notify instantly"),
101 | # TODO: we can add daily summary notification
102 | ], string='Notify on transfer', help='Notify user in telegram about created transfer')
103 |
104 | @api.depends('date', 'periodicity_type', 'periodicity_amount')
105 | def _compute_next_date(self):
106 | for r in self:
107 | if not r.periodicity_type or not r.periodicity_amount:
108 | r.next_date = None
109 | continue
110 | start = r.date
111 | if not start:
112 | start = fields.Datetime.now()
113 | start = fields.Datetime.from_string(start)
114 | days = r.periodicity_amount
115 | if r.periodicity_type == 'week':
116 | days *= 7
117 | elif r.periodicity_type == 'monthly':
118 | # FIXME: this shifts day of month. It must be, for example, ever 21st day of month, instead of +30 days since last date
119 | days *= 30
120 | r.next_date = fields.Datetime.to_string(start + timedelta(days=days))
121 |
122 | @api.model
123 | def action_scheduled_transfers(self):
124 | self.search([('next_date', '!=', False), ('next_date', '<=', fields.Datetime.now())]).action_transfer_now()
125 |
126 | @api.multi
127 | def action_transfer_now(self):
128 | for schedule in self:
129 | # create record
130 | # see em_add_record_from_schedule in api.py file
131 | record = schedule.user_id.partner_id.em_add_record_from_schedule(schedule=schedule)
132 | _logger.debug("Scheduled by %s transfer is created: %s", schedule, record)
133 | # notify
134 | if schedule.notify == 'instantly':
135 | command = self.env.ref('telegram_expense_manager.command_schedule')
136 | tsession = self.env['telegram.session'].sudo().search([('user_id', '=', schedule.user_id.id)])
137 | command.sudo().send_notifications(tsession=tsession, record=record.sudo())
138 |
139 | # update date
140 | schedule.date = fields.Datetime.now()
141 |
--------------------------------------------------------------------------------
/telegram_expense_manager/security/ir.model.access.csv:
--------------------------------------------------------------------------------
1 | id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
2 | access_account_schedule,access_account_schedule,model_account_schedule,base.group_system,1,1,1,1
3 |
--------------------------------------------------------------------------------
/telegram_expense_manager/static/description/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram_expense_manager/static/description/icon.png
--------------------------------------------------------------------------------
/telegram_expense_manager/views/account_account.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | account.account.form
7 | account.account
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/telegram_expense_manager/views/schedule.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | account.schedule.form
7 | account.schedule
8 |
9 |
37 |
38 |
39 |
40 |
41 | account.schedule.tree
42 | account.schedule
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | Schedules
60 | account.schedule
61 | form
62 | tree,form
63 |
64 |
65 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/telegram_mail/README.rst:
--------------------------------------------------------------------------------
1 | .. image:: https://itpp.dev/images/infinity-readme.png
2 | :alt: Tested and maintained by IT Projects Labs
3 | :target: https://itpp.dev
4 |
5 | ===============
6 | Telegram mail
7 | ===============
8 |
9 | Adds commands:
10 |
11 | * ``/mail`` - Subscribe to all incoming messages
12 | * ``/mail_channels`` - Subscribe to selected channels
13 |
14 | 8.0 note
15 | --------
16 |
17 | Command ``/mail_channels`` has more sense in in 9.0+ versions, but we add it to 8.0 too in order to simplify development workflow.
18 |
19 | Questions?
20 | ==========
21 |
22 | To get an assistance on this module contact us by email :arrow_right: help@itpp.dev
23 |
24 | Contributors
25 | ============
26 | * Ivan Yelizariev
27 |
28 | Further information
29 | ===================
30 |
31 | Odoo Apps Store: https://apps.odoo.com/apps/modules/8.0/telegram_mail
32 |
33 |
34 | Tested on `Odoo 8.0 `_
35 |
--------------------------------------------------------------------------------
/telegram_mail/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from . import models
3 |
--------------------------------------------------------------------------------
/telegram_mail/__manifest__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | {
3 | "name": """Telegram Mail""",
4 | "summary": """Bot commands for Mail application""",
5 | "category": "Telegram",
6 | "images": [],
7 | "version": "1.0.0",
8 |
9 | "author": "IT-Projects LLC",
10 | "support": "apps@it-projects.info",
11 | "website": "https://twitter.com/OdooFree",
12 | "license": "LGPL-3",
13 |
14 | "depends": [
15 | "telegram",
16 | "mail",
17 | ],
18 | "external_dependencies": {},
19 | "data": [
20 | "data/ir_action_rules.xml",
21 | "data/command.xml",
22 | "views/mail_channel.xml",
23 | ],
24 | "qweb": [
25 | ],
26 | "demo": [
27 | ],
28 |
29 | "installable": True,
30 | "auto_install": False,
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/telegram_mail/data/command.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | /mail
6 | Subscribe to incoming odoo mail
7 |
8 | subscription
9 | True
10 |
11 |
12 |
13 |
14 | You have subscribed to your new mail messages in odoo.
15 | You have opted out.
16 |
17 | data = {}
18 | data['message'] = env['mail.message'].browse(telegram['event']['active_id'])
19 | data['message_from'] = data['message'].author_id.name
20 | data['message_subject'] = data['message'].subject
21 | data['message_date'] = data['message'].date
22 | data['message_body'] = tools.html2plaintext(data['message'].body)
23 | partner_ids = data['message'].partner_ids + data['message'].channel_ids.mapped('channel_partner_ids')
24 | options['notify_user_ids'] = partner_ids.mapped('user_ids').ids
25 |
26 |
27 |
28 | New message!
29 | From:
30 | Subject:
31 | Time:
32 | Text:
33 |
34 |
35 |
36 |
37 |
38 | /mail_channels
39 | Subscribe to selected channels
40 |
41 | subscription
42 | True
43 |
44 |
45 |
46 | data['current_channels'] = env.user.partner_id.telegram_subscribed_channel_ids
47 |
48 |
49 | You have subscribed to new messages from selected channels.
50 | Currently, you haven't specified channels. To do that open odoo and switch on Notify to Telegram checkbox.
51 | Current channels are:
52 | *
53 |
54 |
55 | You have opted out.
56 |
57 | data = {}
58 | notify_user_ids = []
59 | message = env['mail.message'].browse(telegram['event']['active_id'])
60 | partners = message.channel_ids.mapped('telegram_subscriber_ids')
61 | data['message_channel'] = ", ".join(message.channel_ids.mapped('name'))
62 |
63 | if message.author_id:
64 | partners -= message.author_id
65 |
66 | if partners:
67 | notify_user_ids = partners.mapped('user_ids').ids
68 |
69 | if notify_user_ids:
70 | data['message_from'] = message.author_id.name
71 | data['message_subject'] = message.subject
72 | data['message_date'] = message.date
73 | data['message_body'] = tools.html2plaintext(message.body)
74 | options['notify_user_ids'] = notify_user_ids
75 |
76 |
77 |
78 | New message!
79 | Channel:
80 | From:
81 | Subject:
82 | Time:
83 | Text:
84 |
85 |
86 |
87 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/telegram_mail/data/ir_action_rules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Telegram: broadcast notifications about new mail.message
6 |
7 | 61
8 | on_create
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/telegram_mail/doc/changelog.rst:
--------------------------------------------------------------------------------
1 | `1.0.0`
2 | -------
3 |
4 | - Init version
5 |
--------------------------------------------------------------------------------
/telegram_mail/doc/index.rst:
--------------------------------------------------------------------------------
1 | ===============
2 | Telegram mail
3 | ===============
4 |
5 | /mail
6 | =====
7 |
8 | * Send /mail to the bot
9 | * Send a message to yourself via another user -- you will receive notification via bot
10 |
11 | /mail_channel
12 | =============
13 | * Open Channel Settings
14 | * click ``[Edit]``
15 | * mark checkbox **Notify to Telegram**
16 | * click ``[Save]``
17 | * You you was subscribed to channel notification globally, you will be subscribed and notified about via bot
18 | * Send a message to the channel via another user -- you will receive notification to the bot
19 |
--------------------------------------------------------------------------------
/telegram_mail/models/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from . import mail_channel
3 | from . import res_partner
4 |
--------------------------------------------------------------------------------
/telegram_mail/models/mail_channel.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from odoo import models, fields, api
3 |
4 |
5 | class Channel(models.Model):
6 |
7 | _inherit = 'mail.channel'
8 |
9 | telegram_subscribed = fields.Boolean(
10 | 'Notify to Telegram', compute='_compute_subscribed',
11 | inverse='_inverse_subscribed',
12 | help='Personal setting to get copies of messages via telegram bot. '
13 | 'Use command /mail_channels '
14 | 'to switch on/off notifications globally')
15 |
16 | telegram_subscriber_ids = fields.Many2many(
17 | 'res.partner', 'telegram_mail_channel_partner',
18 | 'channel_id', 'partner_id', string='Telegram Subscribers')
19 |
20 | @api.multi
21 | def _inverse_subscribed(self):
22 | cur_partner = self.env.user.partner_id
23 | for r in self:
24 | if r.telegram_subscribed:
25 | # new value is "subscribed"
26 | value = [(4, cur_partner.id, 0)]
27 | mail_channels_command = self.env.ref('telegram_mail.mail_channels_command', raise_if_not_found=False)
28 | cur_user = cur_partner.user_ids[0]
29 | if not cur_partner.telegram_subscribed_channel_ids \
30 | and cur_user \
31 | and mail_channels_command:
32 | # it's first subscribed channel
33 | mail_channels_command.sudo().subscribe_user(cur_user)
34 | else:
35 | value = [(3, cur_partner.id, 0)]
36 | r.write({'telegram_subscriber_ids': value})
37 |
38 | @api.multi
39 | def _compute_subscribed(self):
40 | cur_partner_id = self.env.user.partner_id.id
41 | for record in self:
42 | record.telegram_subscribed = cur_partner_id in record.telegram_subscriber_ids.ids
43 |
--------------------------------------------------------------------------------
/telegram_mail/models/res_partner.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from odoo import models, fields
3 |
4 |
5 | class ResPartner(models.Model):
6 | _inherit = "res.partner"
7 |
8 | telegram_subscribed_channel_ids = fields.Many2many(
9 | 'mail.channel', 'telegram_mail_channel_partner',
10 | 'partner_id', 'channel_id', string='Telegram Channels',
11 | help='The partner is notified about new messages from these channels. '
12 | 'Notifications could be switched off globally by using /mail_channels command. '
13 | )
14 |
--------------------------------------------------------------------------------
/telegram_mail/static/description/channel-add-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram_mail/static/description/channel-add-1.png
--------------------------------------------------------------------------------
/telegram_mail/static/description/channel-add-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram_mail/static/description/channel-add-2.png
--------------------------------------------------------------------------------
/telegram_mail/static/description/channel-create.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram_mail/static/description/channel-create.png
--------------------------------------------------------------------------------
/telegram_mail/static/description/channel-notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram_mail/static/description/channel-notification.png
--------------------------------------------------------------------------------
/telegram_mail/static/description/channel-settings-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram_mail/static/description/channel-settings-1.png
--------------------------------------------------------------------------------
/telegram_mail/static/description/channel-settings-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram_mail/static/description/channel-settings-2.png
--------------------------------------------------------------------------------
/telegram_mail/static/description/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram_mail/static/description/icon.png
--------------------------------------------------------------------------------
/telegram_mail/static/description/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Telegram Mail
5 | Bot commands for CRM application
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
/mail
14 |
15 | Subscribe to new messages.
16 |
17 |
18 |
19 |

20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
/mail_channels
28 |
29 | Get notification on selected channels only. Allows to get instant notifications about important threads.
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | Create new channel
40 |
41 |
42 |
43 |

44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | Switch Notify to Telegram on.
53 |
54 |
55 |
56 |

57 |
58 |
59 |

60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | Add channel to the followers of important Lead, Task etc.
69 |
70 |
71 |
72 |

73 |
74 |
75 |

76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | Get notifications on new messages.
85 |
86 |
87 |
88 |

89 |
90 |
91 |
92 |
93 |
105 |
--------------------------------------------------------------------------------
/telegram_mail/static/description/mail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram_mail/static/description/mail.png
--------------------------------------------------------------------------------
/telegram_mail/views/mail_channel.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | mail.channel.form
6 | mail.channel
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/telegram_portal/README.rst:
--------------------------------------------------------------------------------
1 | .. image:: https://itpp.dev/images/infinity-readme.png
2 | :alt: Tested and maintained by IT Projects Labs
3 | :target: https://itpp.dev
4 |
5 | =================
6 | Telegram Portal
7 | =================
8 |
9 | Auto sign-up new users.
10 |
11 | * allows user to select language
12 | * hides /login /logout /me commands
13 |
14 | Questions?
15 | ==========
16 |
17 | To get an assistance on this module contact us by email :arrow_right: help@itpp.dev
18 |
19 | Contributors
20 | ============
21 | * Ivan Yelizariev
22 |
23 |
24 | Further information
25 | ===================
26 |
27 | Odoo Apps Store: https://apps.odoo.com/apps/modules/10.0/telegram_portal/
28 |
29 |
30 | Tested on `Odoo 10.0 `_
31 |
--------------------------------------------------------------------------------
/telegram_portal/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram_portal/__init__.py
--------------------------------------------------------------------------------
/telegram_portal/__manifest__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | {
3 | "name": """Telegram Portal""",
4 | "summary": """Auto sign-up new users""",
5 | "category": "Telegram",
6 | "images": [],
7 | "version": "1.0.0",
8 | "application": False,
9 |
10 | "author": "IT-Projects LLC, Ivan Yelizariev",
11 | "support": "apps@it-projects.info",
12 | "website": "https://twitter.com/yelizariev",
13 | "license": "LGPL-3",
14 |
15 | "depends": [
16 | "telegram",
17 | ],
18 | "external_dependencies": {"python": [], "bin": []},
19 | "data": [
20 | "data/auth_signup_data.xml",
21 | "data/telegram_command.xml",
22 | ],
23 | "qweb": [
24 | ],
25 | "demo": [
26 | ],
27 |
28 | "post_load": None,
29 | "pre_init_hook": None,
30 | "post_init_hook": None,
31 |
32 | "auto_install": False,
33 | "installable": True,
34 | }
35 |
--------------------------------------------------------------------------------
/telegram_portal/data/auth_signup_data.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Template Telegram User
7 | telegram_template
8 |
9 |
10 |
11 |
12 |
13 |
14 | telegram_portal.template_user_id
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/telegram_portal/data/telegram_command.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | /start
19 | Start here
20 |
21 |
22 | ask_lang = False
23 | finish = False
24 | if env.ref('base.public_user').id == env.user.id:
25 | # register new user
26 | template_user_id = env['ir.config_parameter'].get_param('telegram_portal.template_user_id')
27 | template_user = env['res.users'].browse(int(template_user_id))
28 | tuser = telegram['tmessage'].from_user
29 | login = 'telegram_%s' % tuser.id
30 | user = env['res.users'].sudo().with_context(active_test=False).search([('login', '=', login)])
31 |
32 | if user:
33 | telegram['tsession'].write({'user_id': user.id})
34 | finish = True
35 | else:
36 | name = ''
37 | #if tuser.username:
38 | # name = '@' + tuser.username
39 | name = '{} {}'.format(tuser.first_name, tuser.last_name)
40 | name = name.strip()
41 |
42 | user = template_user.sudo().copy({
43 | 'login': login,
44 | 'name': name,
45 | 'active': True,
46 | })
47 | telegram['tsession'].write({'user_id': user.id})
48 |
49 | ask_lang = True
50 | else:
51 | user = env.user
52 |
53 | if telegram.get('callback_data'):
54 | env.user.write({'lang': telegram.get('callback_data')})
55 | finish = True
56 | elif not finish:
57 | ask_lang = True
58 |
59 | if ask_lang:
60 | languages = env['res.lang'].get_installed()
61 |
62 | _logger.debug('languages %s', languages)
63 | if len(languages) > 1:
64 | # ask for his language
65 | data['type'] = 'choose_language'
66 | buttons = [{'text': text, 'callback_data': code} for (code, text) in languages]
67 | command.keyboard_buttons(options, buttons, row_width=1)
68 | else:
69 | finish = True
70 |
71 | if finish:
72 | data['type'] = 'finish'
73 | data['name'] = user.name
74 |
75 |
76 |
77 |
78 | Choose your language
79 |
80 |
81 | Welcome, !
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/telegram_portal/doc/changelog.rst:
--------------------------------------------------------------------------------
1 | `1.0.0`
2 | -------
3 |
4 | - Init version
5 |
--------------------------------------------------------------------------------
/telegram_portal/doc/index.rst:
--------------------------------------------------------------------------------
1 | =================
2 | Telegram Portal
3 | =================
4 |
5 | Installation
6 | ============
7 |
8 | * Follow instruction for `Telegram Bot `__ module.
9 | * `Install `__ this module in a usual way
10 |
11 | Configuration
12 | =============
13 |
14 | * At ``Settings >> Translations >> Load a Translation`` install necessary languages
15 |
16 | Usage
17 | =====
18 |
19 | * Send /start to telegram bot
20 | * You are asked to select language (if more than one is available)
21 | * New odoo user is created and authenticated for this telegram user
22 |
23 | Uninstallation
24 | ==============
25 |
26 | After deinstallation, upgrade ``telegram`` module.
27 |
--------------------------------------------------------------------------------
/telegram_portal/static/description/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram_portal/static/description/icon.png
--------------------------------------------------------------------------------
/telegram_project_issue/README.rst:
--------------------------------------------------------------------------------
1 | .. image:: https://itpp.dev/images/infinity-readme.png
2 | :alt: Tested and maintained by IT Projects Labs
3 | :target: https://itpp.dev
4 |
5 | =============
6 | Module Name
7 | =============
8 |
9 | Bot commands for Project Issue application.
10 |
11 | Questions?
12 | ==========
13 |
14 | To get an assistance on this module contact us by email :arrow_right: help@itpp.dev
15 |
16 | Contributors
17 | ============
18 | * Ivan Yelizariev
19 |
20 | Further information
21 | ===================
22 |
23 | Odoo Apps Store: https://apps.odoo.com/apps/modules/9.0/telegram_project_issue/
24 |
25 |
26 | Tested on `Odoo 9.0 `_
27 |
--------------------------------------------------------------------------------
/telegram_project_issue/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram_project_issue/__init__.py
--------------------------------------------------------------------------------
/telegram_project_issue/__manifest__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | {
3 | "name": """Telegram Project Issues""",
4 | "summary": """Bot commands for Project Issue application""",
5 | "category": "Telegram",
6 | "images": [],
7 | "version": "1.0.0",
8 | "application": False,
9 |
10 | "author": "IT-Projects LLC, Ivan Yelizariev",
11 | "support": "apps@it-projects.info",
12 | "website": "https://twitter.com/OdooFree",
13 | "license": "LGPL-3",
14 |
15 | "depends": [
16 | "telegram",
17 | "project_issue",
18 | ],
19 | "external_dependencies": {"python": [], "bin": []},
20 | "data": [
21 | "data/telegram_command.xml",
22 | "data/ir_cron.xml",
23 | ],
24 | "qweb": [
25 | ],
26 | "demo": [
27 | ],
28 |
29 | "post_load": None,
30 | "pre_init_hook": None,
31 | "post_init_hook": None,
32 |
33 | "auto_install": False,
34 | "installable": True,
35 | }
36 |
--------------------------------------------------------------------------------
/telegram_project_issue/data/ir_cron.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Telegram: send /new_issues
7 |
8 |
9 | 1
10 | hours
11 | -1
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/telegram_project_issue/data/telegram_command.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | /new_issues
7 | Subscribe to get report on new issues every hour
8 | subscription
9 |
10 | False
11 |
12 |
13 | You have subscribed to get report of new issues every hour.
14 | You have opted out.
15 |
16 |
17 | ', limit_date_str)
22 | ])
23 | ]]>
24 |
25 |
26 |
27 |
28 | New Issues for the last hour:
29 |
30 |
31 | Contact:
32 | Project:
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/telegram_project_issue/doc/changelog.rst:
--------------------------------------------------------------------------------
1 | Changelog
2 | =========
3 |
4 | `1.0.0`
5 | -------
6 |
7 | - Init version
8 |
--------------------------------------------------------------------------------
/telegram_project_issue/doc/index.rst:
--------------------------------------------------------------------------------
1 | ==============
2 | Telegram CRM
3 | ==============
4 |
5 | Usage
6 | =====
7 |
8 | * send to Bot::
9 |
10 | /new_issues
11 |
12 | * the Bot will subscribe you for new issues and will report about new issues every hour
13 |
--------------------------------------------------------------------------------
/telegram_project_issue/static/description/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram_project_issue/static/description/icon.png
--------------------------------------------------------------------------------
/telegram_project_issue/static/description/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Telegram Project Issues
5 | Bot commands for Project Issue application
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
/new_issues
14 |
15 | Subscribe to new issues.
16 |
17 |
18 |
19 |

20 |
21 |
22 |
23 |
24 |
36 |
--------------------------------------------------------------------------------
/telegram_project_issue/static/description/new_issues.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram_project_issue/static/description/new_issues.png
--------------------------------------------------------------------------------
/telegram_shop/README.rst:
--------------------------------------------------------------------------------
1 | .. image:: https://itpp.dev/images/infinity-readme.png
2 | :alt: Tested and maintained by IT Projects Labs
3 | :target: https://itpp.dev
4 |
5 | ===============
6 | Telegram Shop
7 | ===============
8 |
9 | Environment for some shop representation.
10 |
11 |
12 | Questions?
13 | ==========
14 |
15 | To get an assistance on this module contact us by email :arrow_right: help@itpp.dev
16 |
17 | Contributors
18 | ============
19 | * Ilyas
20 |
21 | Further information
22 | ===================
23 |
24 | Odoo Apps Store: https://apps.odoo.com/apps/modules/8.0/telegram_shop/
25 |
26 |
27 | Tested on `Odoo 8.0 `_
28 |
29 |
--------------------------------------------------------------------------------
/telegram_shop/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from . import telegram_command
3 | from . import controllers
4 |
--------------------------------------------------------------------------------
/telegram_shop/__manifest__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | {
3 | "name": """Telegram Shop""",
4 | "summary": """Bot commands for Shop enviroment""",
5 | "category": "Telegram",
6 | "images": [],
7 | "version": "1.0.0",
8 |
9 | "author": "IT-Projects LLC",
10 | "support": "apps@it-projects.info",
11 | "website": "https://twitter.com/OdooFree",
12 | "license": "LGPL-3",
13 |
14 | "depends": [
15 | "telegram",
16 | "product",
17 | "website_sale",
18 | ],
19 | "external_dependencies": {},
20 | "data": [
21 | "data/command.xml",
22 | ],
23 | "qweb": [
24 | ],
25 | "demo": [
26 | ],
27 |
28 | "installable": True,
29 | "auto_install": False,
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/telegram_shop/controllers/__init__.py:
--------------------------------------------------------------------------------
1 | from . import main
2 |
--------------------------------------------------------------------------------
/telegram_shop/controllers/main.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import werkzeug
4 | from odoo import http
5 | from odoo.http import request
6 |
7 |
8 | class TelegramWebsiteSale(http.Controller):
9 |
10 | @http.route(['/shop/telegram_cart/'], type='http', auth="public", website=True)
11 | def telegram_cart(self, sale_order_id):
12 | sale_order = request.env['sale.order'].sudo().browse(sale_order_id)
13 | if request.env.user.partner_id != sale_order.partner_id:
14 | request.session.logout()
15 | query = werkzeug.urls.url_encode({
16 | 'redirect': request.httprequest.url,
17 | })
18 | return request.redirect('/web/login?%s' % query)
19 | request.session['sale_order_id'] = sale_order.id
20 | return request.redirect('/shop/cart')
21 |
--------------------------------------------------------------------------------
/telegram_shop/data/command.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | /shop
6 | Shows available goods
7 |
8 | normal
9 | True
10 |
11 |
114 |
115 |
116 |
117 |
118 |
119 | Select category:
120 |
121 |
122 | Select product:
123 |
124 |
125 |
126 | Price:
127 |
128 |
129 | Your Cart:
130 |
131 | x
132 |
133 | Checkout on website:
134 |
135 |
136 |
137 |
138 |
139 | Nothing is found in this category
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
--------------------------------------------------------------------------------
/telegram_shop/static/description/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram_shop/static/description/icon.png
--------------------------------------------------------------------------------
/telegram_shop/static/description/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Telegram Shop
5 | Bot commands for Shop enviroment
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
/shop
14 |
15 | Allows customer to make order via Telegram.
16 |
17 |
18 |
19 |

20 |
21 |
22 |
23 |
24 |
36 |
--------------------------------------------------------------------------------
/telegram_shop/static/description/telegram_shop.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/itpp-labs/odoo-telegram/09925af00a9711d6c3125f7ed952276676b131c2/telegram_shop/static/description/telegram_shop.gif
--------------------------------------------------------------------------------
/telegram_shop/telegram_command.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from odoo import models, api
3 |
4 |
5 | class TelegramCommand(models.Model):
6 | _inherit = 'telegram.command'
7 |
8 | @api.model
9 | def get_pricelist(self, context):
10 | sale_order = context.get('sale_order')
11 | if sale_order:
12 | sale_order = self.env['sale.order'].sudo().browse(sale_order)
13 | pricelist = sale_order.pricelist_id
14 | else:
15 | partner = self.env.user.partner_id
16 | pricelist = partner.property_product_pricelist
17 | return pricelist
18 |
19 | @api.model
20 | def sale_get_order(self, context):
21 | sale_order = context.get('sale_order')
22 | if sale_order:
23 | sale_order = self.env['sale.order'].sudo().browse(sale_order)
24 |
25 | if sale_order:
26 | return sale_order
27 |
28 | user = self.env.user
29 | partner = user.partner_id
30 | values = {
31 | 'user_id': user.id,
32 | 'partner_id': partner.id,
33 | 'pricelist_id': partner.property_product_pricelist.id,
34 | 'section_id': self.env.ref('sales_team.salesteam_website_sales').id,
35 | }
36 | sale_order = self.env['sale.order'].sudo().create(values)
37 | context['sale_order'] = sale_order.id
38 | return sale_order
39 |
--------------------------------------------------------------------------------