sendethic: add send ethic sms connector (#81143)
gitea/passerelle/pipeline/head This commit looks good Details

This commit is contained in:
Corentin Sechet 2023-09-18 19:56:49 +02:00
parent 5fe9b9a1e6
commit 199075ed80
5 changed files with 258 additions and 0 deletions

View File

View File

@ -0,0 +1,116 @@
# Generated by Django 3.2.18 on 2023-09-18 17:55
import django.contrib.postgres.fields
import django.core.validators
from django.db import migrations, models
import passerelle.sms.models
class Migration(migrations.Migration):
initial = True
dependencies = [
('base', '0030_resourcelog_base_resour_appname_298cbc_idx'),
]
operations = [
migrations.CreateModel(
name='SendEthicSMSGateway',
fields=[
(
'id',
models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
('title', models.CharField(max_length=50, verbose_name='Title')),
('slug', models.SlugField(unique=True, verbose_name='Identifier')),
('description', models.TextField(verbose_name='Description')),
(
'default_country_code',
models.CharField(
default='33',
max_length=3,
validators=[
django.core.validators.RegexValidator(
'^[0-9]*$', 'The country must only contain numbers'
)
],
verbose_name='Default country code',
),
),
(
'default_trunk_prefix',
models.CharField(
default='0',
max_length=2,
validators=[
django.core.validators.RegexValidator(
'^[0-9]*$', 'The trunk prefix must only contain numbers'
)
],
verbose_name='Default trunk prefix',
),
),
(
'max_message_length',
models.IntegerField(
default=2000,
help_text='Messages over this limit will be truncated.',
verbose_name='Maximum message length',
),
),
(
'authorized',
django.contrib.postgres.fields.ArrayField(
base_field=models.CharField(
choices=[
('fr-metro', 'France mainland (+33 [67])'),
('fr-domtom', 'France DOM/TOM (+262, etc.)'),
('be', 'Belgian (+32 4[5-9]) '),
('all', 'All'),
],
max_length=32,
null=True,
),
default=passerelle.sms.models.authorized_default,
size=None,
verbose_name='Authorized Countries',
),
),
(
'credit_threshold_alert',
models.PositiveIntegerField(default=500, verbose_name='Credit alert threshold'),
),
(
'credit_left',
models.PositiveIntegerField(default=0, editable=False, verbose_name='Credit left'),
),
(
'alert_emails',
django.contrib.postgres.fields.ArrayField(
base_field=models.EmailField(blank=True, max_length=254),
blank=True,
null=True,
size=None,
verbose_name='Email addresses list to send credit alerts to, separated by comma',
),
),
('credit_alert_timestamp', models.DateTimeField(editable=False, null=True)),
('account_id', models.CharField(max_length=255, verbose_name='Account ID')),
('api_key', models.CharField(max_length=255, verbose_name='API Key')),
(
'users',
models.ManyToManyField(
blank=True,
related_name='_sendethic_sendethicsmsgateway_users_+',
related_query_name='+',
to='base.ApiUser',
),
),
],
options={
'verbose_name': 'Sendethic',
'db_table': 'sms_send_ethic',
},
),
]

View File

@ -0,0 +1,141 @@
# passerelle - uniform access to multiple data sources and services
# Copyright (C) 2022 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import urllib.parse
import requests
from django.db import models
from django.utils.translation import gettext_lazy as _
from passerelle.sms.models import TrackCreditSMSResource
from passerelle.utils.jsonresponse import APIError
class SendEthicSMSGateway(TrackCreditSMSResource):
account_id = models.CharField(verbose_name=_('Account ID'), max_length=255)
api_key = models.CharField(verbose_name=_('API Key'), max_length=255)
# unecessary field
allow_premium_rate = None
class Meta:
verbose_name = 'Sendethic'
db_table = 'sms_send_ethic'
TEST_DEFAULTS = {
'create_kwargs': {
'account_id': 'yyy',
'api_key': 'www',
'credit_threshold_alert': 1000,
},
'test_vectors': [
{
'status_code': 400,
'response': {'Message': 'Grève des PTT.'},
'result': {
'err': 1,
'err_desc': 'Sendethic error: some destinations failed',
'data': [],
},
},
{
'status_code': 400,
'response': {'no_error_field_in_message': True},
'result': {
'err': 1,
'err_desc': 'Sendethic error: some destinations failed',
'data': [],
},
},
{
'status_code': 200,
'response': '"OK"',
'result': {
'err': 0,
'data': {},
},
},
],
}
URL = 'https://services.message-business.com/api/rest/v4/'
TEST_CREDIT_LEFT = {
'create_kwargs': {
'account_id': 'yyy',
'api_key': 'www',
},
'url': urllib.parse.urljoin(URL, 'account/remainingcredits/sms'),
'get_credit_left_payload': lambda x: {'used': 1, 'remaining': x, 'total': 2000},
}
def request(self, method, endpoint, **kwargs):
url = urllib.parse.urljoin(self.URL, endpoint)
headers = {'Accept': 'application/json'}
try:
response = self.requests.request(
method,
url,
headers=headers,
auth=(self.account_id, self.api_key),
**kwargs,
)
except requests.RequestException as e:
return False, 'Request failed, %s' % e
try:
result = response.json()
if not response.ok:
result = result['Message']
except (ValueError, KeyError):
return False, 'Bad JSON response'
return response.ok, result
def send_msg(self, text, sender, destinations, **kwargs):
# Destination numbers received here were normalized through
# self.clean_number() : they are of the form 00612345678. Remove
# the trailing 00 and replace it with '+', so Sendethic accepts them.
destinations = [f'+{dest[2:]}' for dest in destinations]
errors = []
for dest in destinations:
params = {
'message': text,
'mobile': dest,
}
if sender:
params['oadc'] = sender
success, message = self.request('post', 'sms/send/', json=params)
if not success:
errors.append(message)
if errors:
raise APIError('Sendethic error: some destinations failed', data=errors)
def update_credit_left(self):
success, result = self.request('get', endpoint='account/remainingcredits/sms/')
if not success:
self.logger.warning('Cannot retrieve credits for Sendethic connector: %s', result)
try:
self.credit_left = int(result['remaining'])
self.save(update_fields=['credit_left'])
except KeyError:
self.logger.warning('Cannot retrieve credits for Sendethic connector: %s', result)

View File

@ -186,6 +186,7 @@ INSTALLED_APPS = (
'passerelle.apps.solis',
'passerelle.apps.twilio',
'passerelle.apps.vivaticket',
'passerelle.apps.sendethic',
# backoffice templates and static
'gadjo',
)