|
| 1 | +#!/usr/bin/env python3 |
| 2 | + |
| 3 | +import argparse |
| 4 | +import json |
| 5 | +from os.path import exists |
| 6 | + |
| 7 | +import requests |
| 8 | +import urllib3 |
| 9 | + |
| 10 | +import unicodedata |
| 11 | + |
| 12 | +parser = argparse.ArgumentParser(description='Generate MOTOTRBO contacts file from RadioId.net data') |
| 13 | + |
| 14 | +parser.add_argument('-f', '--force', action='store_true', |
| 15 | + help='Forcibly download user list even if it exists locally') |
| 16 | +parser.add_argument('-c', '--country', required=True, help='Country name' |
| 17 | + 'As per RadioID.net') |
| 18 | + |
| 19 | +args = parser.parse_args() |
| 20 | + |
| 21 | +radioid_url = 'https://radioid.net/api/dmr/user/?' |
| 22 | +radioid_file = 'radioid_' + args.country + '.json' |
| 23 | +existing = {} |
| 24 | + |
| 25 | +def download_file(): |
| 26 | + |
| 27 | + if not exists(radioid_file) or args.force: |
| 28 | + download_ext_path = f'country={args.country}' if args.country else print('Country name is required.') and exit(1) |
| 29 | + |
| 30 | + |
| 31 | + download_full_path = radioid_url + download_ext_path |
| 32 | + |
| 33 | + print(f'Downloading from {download_full_path}') |
| 34 | + |
| 35 | + urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) |
| 36 | + |
| 37 | + response = requests.get(download_full_path, verify=False) |
| 38 | + response.raise_for_status() |
| 39 | + |
| 40 | + with open(radioid_file, 'wb') as file: |
| 41 | + file.write(response.content) |
| 42 | + |
| 43 | + print(f'Saved to {radioid_file}') |
| 44 | + |
| 45 | + |
| 46 | +def process_contacts(): |
| 47 | + f = open(radioid_file, 'r') |
| 48 | + json_list = json.loads(f.read()) |
| 49 | + |
| 50 | + idx = 0 |
| 51 | + channels = '' |
| 52 | + for item in json_list['results']: |
| 53 | + idx += 1 |
| 54 | + channels += format_channel(item) |
| 55 | + |
| 56 | + f.close() |
| 57 | + |
| 58 | + print(f'Processed {idx} records.') |
| 59 | + |
| 60 | + write_zone_file(args.country, |
| 61 | +f'''<?xml version="1.0" encoding="utf-8" standalone="yes"?> |
| 62 | +<config> |
| 63 | + <category name="PCRContacts"> |
| 64 | + {channels} |
| 65 | + </category> |
| 66 | +</config>''') |
| 67 | + |
| 68 | +def format_channel(item): |
| 69 | + global existing |
| 70 | + |
| 71 | + city = unicodedata.normalize('NFKD', item['city']).encode('ascii', 'ignore').decode('utf-8').strip() |
| 72 | + name = unicodedata.normalize('NFKD', item['fname']).encode('ascii', 'ignore').decode('utf-8').strip() |
| 73 | + surname = unicodedata.normalize('NFKD', item['surname']).encode('ascii', 'ignore').decode('utf-8').strip() |
| 74 | + |
| 75 | + # max len 16 chars |
| 76 | + contact_name = f'{item["callsign"]} {name} {surname}'.strip()[:16] |
| 77 | + |
| 78 | + if not contact_name in existing: existing[contact_name] = 0 |
| 79 | + existing[contact_name] += 1 |
| 80 | + if existing[contact_name] > 1: |
| 81 | + # cut contact name to 14 chars and add space and number |
| 82 | + contact_name = f'{contact_name[:14]} {existing[contact_name]}' |
| 83 | + |
| 84 | + return f'''<set name="PCRContacts" alias="{contact_name}"> |
| 85 | + <field name="ContactName">{contact_name}</field> |
| 86 | + <collection name="DigitalCalls"> |
| 87 | + <set name="DigitalCalls" index="0" key="PRIVCALL"> |
| 88 | + <field name="DU_CALLALIAS">{contact_name}</field> |
| 89 | + <field name="DU_CALLLSTID">{item["id"]}</field> |
| 90 | + <field name="DU_ROUTETYPE" Name="Regular">REGULAR</field> |
| 91 | + <field name="DU_CALLPRCDTNEN">False</field> |
| 92 | + <field name="DU_RINGTYPE" Name="No Style">NOSTYLE</field> |
| 93 | + <field name="DU_TXTMSGALTTNTP" Name="Repetitive">REPETITIVE</field> |
| 94 | + <field name="DU_CALLTYPE" Name="Private Call">PRIVCALL</field> |
| 95 | + <field name="DU_OVCMCALL">False</field> |
| 96 | + <field name="DU_CALLTYPEPART2">0</field> |
| 97 | + <field name="DU_UKPOTCFLG">False</field> |
| 98 | + <field name="DU_RVRTPERS_Zone" Name="None">NONE</field> |
| 99 | + <field name="DU_RVRTPERS" Name="Selected">SELECTED</field> |
| 100 | + <field name="CallType">Digital Calls-Private Call</field> |
| 101 | + <field name="PeudoCallId">{item["id"]}</field> |
| 102 | + </set> |
| 103 | + </collection> |
| 104 | + <collection name="CapacityPlusCalls" /> |
| 105 | + <collection name="PhoneCalls" /> |
| 106 | + <field name="Comments"></field> |
| 107 | + </set> |
| 108 | +''' |
| 109 | + |
| 110 | + |
| 111 | +def write_zone_file(zone_alias, contents): |
| 112 | + zone_file_name = zone_alias + ".xml" |
| 113 | + zone_file = open(zone_file_name, "wt") |
| 114 | + zone_file.write(contents) |
| 115 | + zone_file.close() |
| 116 | + print(f'Contact file "{zone_file_name}" written\n') |
| 117 | + |
| 118 | + |
| 119 | +if __name__ == '__main__': |
| 120 | + download_file() |
| 121 | + process_contacts() |
0 commit comments