# -*- 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))