# -*- coding: utf-8 -*-
#
# Copyright (c) 2016 - 2019 -- Lars Heuer - Semagia .
# All rights reserved.
#
# License: BSD License
#
"""\
Additional factory functions for common QR Codes.
The factory functions which return a QR Code with the minimum error correction
level "L" (or better). To create a (Micro) QR Code which should use a specific
error correction level or version etc., use the "_data" factory functions which
return a string which can be used as input for :py:func:`segno.make()`.
"""
from __future__ import absolute_import, unicode_literals
import re
import segno
try: # pragma: no cover
from urllib.parse import urlsplit, quote
str_type = str
except ImportError: # pragma: no cover
from urlparse import urlsplit
from urllib import quote
str = unicode
str_type = basestring
_MECARD_ESCAPE = {
ord('\\'): '\\\\',
ord(';'): '\\;',
ord(':'): '\\:',
ord('"'): '\\"',
}
_VCARD_ESCAPE = {
ord(','): '\\,',
ord(';'): '\\;',
}
def _escape_mecard(s):
"""\
Escapes ``\\``, ``;``, ``"`` and ``:`` in the provided string.
:param str s: The string to escape.
:rtype str
"""
return str(s).translate(_MECARD_ESCAPE)
def _escape_vcard(s):
"""\
Escapes ``\\``, ``;``, ``"`` and ``:`` in the provided string.
:param str s: The string to escape.
:rtype str
"""
return str(s).translate(_VCARD_ESCAPE)
def make_wifi_data(ssid, password, security, hidden=False):
"""\
Creates WIFI configuration string.
:param str ssid: The SSID of the network.
:param str|None password: The password.
:param str|None security: Authentication type; the value should
be "WEP" or "WPA". Set to ``None`` to omit the value.
"nopass" is equivalent to setting the value to ``None`` but in
the former case, the value is not omitted.
:param bool hidden: Indicates if the network is hidden (default: ``False``)
:rtype: str
"""
def quotation_mark(x):
"""\
Returns '"' if x could be interpreted as hexadecimal value, otherwise
an empty string.
See:
[...] Enclose in double quotes if it is an ASCII name, but could be
interpreted as hex (i.e. "ABCD") [...]
"""
try:
int(x, 16)
except ValueError:
return ''
return '"'
escape = _escape_mecard
data = 'WIFI:'
if security:
data += 'T:{0};'.format(security.upper() if security != 'nopass' else security)
data += 'S:{1}{0}{1};'.format(escape(ssid), quotation_mark(ssid))
if password:
data += 'P:{1}{0}{1};'.format(escape(password), quotation_mark(password))
data += 'H:true;' if hidden else ';'
return data
def make_wifi(ssid, password, security, hidden=False):
"""\
Creates a WIFI configuration QR Code.
:param str ssid: The SSID of the network.
:param str|None password: The password.
:param str|None security: Authentication type; the value should
be "WEP" or "WPA". Set to ``None`` to omit the value.
"nopass" is equivalent to setting the value to ``None`` but in
the former case, the value is not omitted.
:param bool hidden: Indicates if the network is hidden (default: ``False``)
:rtype: segno.QRCode
"""
return segno.make_qr(make_wifi_data(ssid, password, security, hidden))
def make_mecard_data(name, reading=None, email=None, phone=None, videophone=None,
memo=None, nickname=None, birthday=None, url=None,
pobox=None, roomno=None, houseno=None, city=None,
prefecture=None, zipcode=None, country=None):
"""\
Creates a string encoding the contact information as MeCard.
:param str name: Name. If it contains a comma, the first part
is treated as lastname and the second part is treated as forename.
:param str|None reading: Designates a text string to be set as the
kana name in the phonebook
:param str|iterable email: E-mail address. Multiple values are
allowed.
:param str|iterable phone: Phone number. Multiple values are
allowed.
:param str|iterable videophone: Phone number for video calls.
Multiple values are allowed.
:param str memo: A notice for the contact.
:param str nickname: Nickname.
:param (str|int|date) birthday: Birthday. If a string is provided,
it should encode the date as YYYYMMDD value.
:param str|iterable url: Homepage. Multiple values are allowed.
:param str|None pobox: P.O. box (address information).
:param str|None roomno: Room number (address information).
:param str|None houseno: House number (address information).
:param str|None city: City (address information).
:param str|None prefecture: Prefecture (address information).
:param str|None zipcode: Zip code (address information).
:param str|None country: Country (address information).
:rtype: str
"""
def make_multifield(name, val):
if val is None:
return ()
if isinstance(val, str_type):
val = (val,)
return ['{0}:{1};'.format(name, escape(i)) for i in val]
escape = _escape_mecard
data = ['MECARD:N:{0};'.format(escape(name))]
if reading:
data.append('SOUND:{0};'.format(escape(reading)))
data.extend(make_multifield('TEL', phone))
data.extend(make_multifield('TELAV', videophone))
data.extend(make_multifield('EMAIL', email))
if nickname:
data.append('NICKNAME:{0};'.format(escape(nickname)))
if birthday:
try:
birthday = birthday.strftime('%Y%m%d')
except AttributeError:
pass
data.append('BDAY:{0};'.format(birthday))
data.extend(make_multifield('URL', url))
adr_properties = (pobox, roomno, houseno, city, prefecture, zipcode, country)
if any(adr_properties):
adr_data = [escape(i or '') for i in adr_properties]
data.append('ADR:{0},{1},{2},{3},{4},{5},{6};'.format(*adr_data))
if memo:
data.append('MEMO:{0};'.format(escape(memo)))
data.append(';')
return ''.join(data)
def make_mecard(name, reading=None, email=None, phone=None, videophone=None,
memo=None, nickname=None, birthday=None, url=None, pobox=None,
roomno=None, houseno=None, city=None, prefecture=None,
zipcode=None, country=None):
"""\
Returns a QR Code which encodes a `MeCard `_
:param str name: Name. If it contains a comma, the first part
is treated as lastname and the second part is treated as forename.
:param str|None reading: Designates a text string to be set as the
kana name in the phonebook
:param str|iterable email: E-mail address. Multiple values are
allowed.
:param str|iterable phone: Phone number. Multiple values are
allowed.
:param str|iterable videophone: Phone number for video calls.
Multiple values are allowed.
:param str memo: A notice for the contact.
:param str nickname: Nickname.
:param str|int|date birthday: Birthday. If a string is provided,
it should encode the date as YYYYMMDD value.
:param str|iterable url: Homepage. Multiple values are allowed.
:param str|None pobox: P.O. box (address information).
:param str|None roomno: Room number (address information).
:param str|None houseno: House number (address information).
:param str|None city: City (address information).
:param str|None prefecture: Prefecture (address information).
:param str|None zipcode: Zip code (address information).
:param str|None country: Country (address information).
:rtype: segno.QRCode
"""
return segno.make_qr(make_mecard_data(name=name, reading=reading,
email=email, phone=phone,
videophone=videophone, memo=memo,
nickname=nickname, birthday=birthday,
url=url, pobox=pobox, roomno=roomno,
houseno=houseno, city=city,
prefecture=prefecture, zipcode=zipcode,
country=country))
_looks_like_datetime = re.compile(r'^\d{4}\-\d{2}\-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:(?:\-?\d{2}:\d{2})|Z)?)?$').match
def make_vcard_data(name, displayname, email=None, phone=None, fax=None,
videophone=None, memo=None, nickname=None, birthday=None,
url=None, pobox=None, street=None, city=None, region=None,
zipcode=None, country=None, org=None, lat=None, lng=None,
source=None, rev=None, title=None, photo_uri=None):
"""\
Creates a string encoding the contact information as vCard 3.0.
Only a subset of available vCard properties is supported.
:param str name: The name. If it contains a semicolon, , the first part
is treated as lastname and the second part is treated as forename.
:param str displayname: Common name.
:param str|iterable email: E-mail address. Multiple values are allowed.
:param str|iterable phone: Phone number. Multiple values are allowed.
:param str|iterable fax: Fax number. Multiple values are allowed.
:param str|iterable videophone: Phone number for video calls.
Multiple values are allowed.
:param str memo: A notice for the contact.
:param str nickname: Nickname.
:param str|date birthday: Birthday. If a string is provided,
it should encode the date as YYYY-MM-DD value.
:param str|iterable url: Homepage. Multiple values are allowed.
:param str|None pobox: P.O. box (address information).
:param str|None street: Street address.
:param str|None city: City (address information).
:param str|None region: Region (address information).
:param str|None zipcode: Zip code (address information).
:param str|None country: Country (address information).
:param str org: Company / organization name.
:param float lat: Latitude.
:param float lng: Longitude.
:param str source: URL where to obtain the vCard.
:param str|date rev: Revision of the vCard / last modification date.
:param str|iterable|None title: Job Title. Multiple values are allowed.
:param str|iterable|None photo_uri: Photo URI. Multiple values are allowed.
:rtype: str
"""
def make_multifield(name, val):
if val is None:
return ()
if isinstance(val, str_type):
val = (val,)
return ['{0}:{1}'.format(name, escape(i)) for i in val]
escape = _escape_vcard
data = ['BEGIN:VCARD', 'VERSION:3.0',
'N:{0}'.format(name),
'FN:{0}'.format(escape(displayname))]
if org:
data.append('ORG:{0}'.format(escape(org)))
data.extend(make_multifield('EMAIL', email))
data.extend(make_multifield('TEL', phone))
data.extend(make_multifield('TEL;TYPE=FAX', fax))
data.extend(make_multifield('TEL;TYPE=VIDEO', videophone))
data.extend(make_multifield('URL', url))
data.extend(make_multifield('TITLE', title))
data.extend(make_multifield('PHOTO;VALUE=uri', photo_uri))
if nickname:
data.append('NICKNAME:{0}'.format(escape(nickname)))
adr_properties = (pobox, street, city, region, zipcode, country)
if any(adr_properties):
adr_data = [escape(i or '') for i in adr_properties]
data.append('ADR:{0};;{1};{2};{3};{4};{5}'.format(*adr_data))
if birthday:
try:
birthday = birthday.strftime('%Y-%m-%d')
except AttributeError:
pass
if not _looks_like_datetime(birthday):
raise ValueError('"birthday" does not seem to be a valid date or date/time representation')
data.append('BDAY:{0};'.format(birthday))
if lat or lng and (not(all((lat, lng)))):
raise ValueError('Incomplete geo information, please specify latitude and longitude.')
if lat and lng:
data.append('GEO:{0};{1}'.format(lat, lng))
if source:
data.append('SOURCE:{0}'.format(escape(url)))
if memo:
data.append('NOTE:{0}'.format(escape(memo)))
if rev:
if not _looks_like_datetime(rev):
raise ValueError('"rev" does not seem to be a valid date or date/time representation')
data.append('REV:{0}'.format(rev))
data.append('END:VCARD')
data.append('')
return '\r\n'.join(data)
def make_vcard(name, displayname, email=None, phone=None, fax=None,
videophone=None, memo=None, nickname=None, birthday=None,
url=None, pobox=None, street=None, city=None, region=None,
zipcode=None, country=None, org=None, lat=None, lng=None,
source=None, rev=None, title=None):
"""\
Creates a QR Code which encodes a `vCard `_
version 3.0.
Only a subset of available vCard properties is supported.
:param str name: The name. If it contains a semicolon, , the first part
is treated as lastname and the second part is treated as forename.
:param str displayname: Common name.
:param str|iterable email: E-mail address. Multiple values are allowed.
:param str|iterable phone: Phone number. Multiple values are allowed.
:param str|iterable fax: Fax number. Multiple values are allowed.
:param str|iterable videophone: Phone number for video calls.
Multiple values are allowed.
:param str memo: A notice for the contact.
:param str nickname: Nickname.
:param str|date birthday: Birthday. If a string is provided,
it should encode the date as YYYY-MM-DD value.
:param str|iterable url: Homepage. Multiple values are allowed.
:param str|None pobox: P.O. box (address information).
:param str|None street: Street address.
:param str|None city: City (address information).
:param str|None region: Region (address information).
:param str|None zipcode: Zip code (address information).
:param str|None country: Country (address information).
:param str org: Company / organization name.
:param float lat: Latitude.
:param float lng: Longitude.
:param str source: URL where to obtain the vCard.
:param str|date rev: Revision of the vCard / last modification date.
:param str|iterable|None title: Job Title. Multiple values are allowed.
:rtype: segno.QRCode
"""
return segno.make_qr(make_vcard_data(name, displayname, email=email,
phone=phone, fax=fax,
videophone=videophone, memo=memo,
nickname=nickname, birthday=birthday,
url=url, pobox=pobox, street=street,
city=city, region=region,
zipcode=zipcode, country=country,
org=org, lat=lat, lng=lng,
source=source, rev=rev, title=title))
def make_geo_data(lat, lng):
"""\
Creates a geo location URI.
:param float lat: Latitude
:param float lng: Longitude
:rtype: str
"""
def float_to_str(f):
return '{0:.8f}'.format(f).rstrip('0')
return 'geo:{0},{1}'.format(float_to_str(lat), float_to_str(lng))
def make_geo(lat, lng):
"""\
Returns a QR Code which encodes geographic location using the ``geo`` URI
scheme.
:param float lat: Latitude
:param float lng: Longitude
:rtype: segno.QRCode
"""
return segno.make_qr(make_geo_data(lat, lng))
def make_make_email_data(to, cc=None, bcc=None, subject=None, body=None):
"""\
Creates either a simple "mailto:" URL or complete e-mail message with
(blind) carbon copies and a subject and a body.
:param str|iterable to: The email address (recipient). Multiple
values are allowed.
:param str|iterable|None cc: The carbon copy recipient. Multiple
values are allowed.
:param str|iterable|None bcc: The blind carbon copy recipient.
Multiple values are allowed.
:param str|None subject: The subject.
:param str|None body: The message body.
"""
def multi(val):
if not val:
return ()
if isinstance(val, str_type):
return (val,)
return tuple(val)
delim = '?'
data = ['mailto:']
if not to:
raise ValueError('"to" must not be empty or None')
data.append(','.join(multi(to)))
for key, val in (('cc', cc), ('bcc', bcc)):
vals = multi(val)
if vals:
data.append('{0}{1}={2}'.format(delim, key, ','.join(vals)))
delim = '&'
for key, val in (('subject', subject), ('body', body)):
if val is not None:
data.append('{0}{1}={2}'.format(delim, key, quote(val.encode('utf-8'))))
delim = '&'
return ''.join(data)
def make_email(to, cc=None, bcc=None, subject=None, body=None):
"""\
Encodes either a simple e-mail address or a complete message with
(blind) carbon copies and a subject and a body.
:param str|iterable to: The email address (recipient). Multiple
values are allowed.
:param str|iterable|None cc: The carbon copy recipient. Multiple
values are allowed.
:param str|iterable|None bcc: The blind carbon copy recipient.
Multiple values are allowed.
:param str|None subject: The subject.
:param str|None body: The message body.
"""
return segno.make_qr(make_make_email_data(to=to, cc=cc, bcc=bcc,
subject=subject, body=body))