2022-05-19 14:15:19 +02:00
|
|
|
#!/usr/bin/env python3
|
2022-05-19 15:59:26 +02:00
|
|
|
|
|
|
|
|
|
|
|
# Infos on colors: https://pypi.org/project/colored/
|
|
|
|
# Country list extracted from http://www.arrl.org/files/file/dxcclist.txt
|
|
|
|
|
|
|
|
|
2022-05-18 23:39:02 +02:00
|
|
|
import sys
|
|
|
|
import csv
|
|
|
|
import re
|
|
|
|
import json
|
|
|
|
import os
|
2022-05-19 14:15:19 +02:00
|
|
|
import time as bla
|
2022-05-18 23:39:02 +02:00
|
|
|
from telnetlib import Telnet
|
|
|
|
from colored import fg, bg, attr
|
|
|
|
import configparser
|
2022-05-19 14:15:19 +02:00
|
|
|
from collections import defaultdict
|
2022-05-19 15:59:26 +02:00
|
|
|
import random
|
2022-05-20 14:45:09 +02:00
|
|
|
import requests
|
|
|
|
from os.path import exists
|
|
|
|
import zipfile
|
2022-05-18 23:39:02 +02:00
|
|
|
|
|
|
|
class ColorSpot():
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
self.version = "0.1.0"
|
|
|
|
|
2022-05-20 14:45:09 +02:00
|
|
|
self.print_banner()
|
|
|
|
|
2022-05-18 23:39:02 +02:00
|
|
|
self.config = configparser.ConfigParser()
|
|
|
|
self.config_file = os.path.expanduser('~/.colorspot.ini')
|
|
|
|
self.read_config(self.config, self.config_file)
|
|
|
|
|
2022-05-20 14:45:09 +02:00
|
|
|
self.check_files()
|
|
|
|
if self.check_lotw_confirmed:
|
|
|
|
self.confirmed_entities = self.get_confirmed_entities()
|
2022-05-18 23:39:02 +02:00
|
|
|
|
2022-05-20 14:45:09 +02:00
|
|
|
|
|
|
|
if self.check_cty:
|
|
|
|
self.cty = list(csv.reader(open(self.config['files']['cty'], "r"), delimiter=","))
|
2022-05-19 15:59:26 +02:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def rnd_col():
|
|
|
|
r = lambda: random.randint(0,255)
|
|
|
|
return'#%02X%02X%02X' % (r(),r(),r())
|
|
|
|
|
2022-05-20 14:45:09 +02:00
|
|
|
@staticmethod
|
|
|
|
def download_file(url, local_filename):
|
|
|
|
#local_filename = url.split('/')[-1]
|
|
|
|
# NOTE the stream=True parameter below
|
|
|
|
with requests.get(url, stream=True) as r:
|
|
|
|
r.raise_for_status()
|
|
|
|
with open(local_filename, 'wb') as f:
|
|
|
|
for chunk in r.iter_content(chunk_size=8192):
|
|
|
|
# If you have chunk encoded response uncomment if
|
|
|
|
# and set chunk_size parameter to None.
|
|
|
|
#if chunk:
|
|
|
|
f.write(chunk)
|
|
|
|
return local_filename
|
|
|
|
|
|
|
|
def check_files(self):
|
|
|
|
# check for lotw qsl information file
|
|
|
|
self.check_lotw_confirmed = exists(self.config['files']['lotw_confirmed'])
|
|
|
|
if not self.check_lotw_confirmed:
|
|
|
|
print("The file " + self.config['files']['lotw_confirmed'] + " is missing.")
|
|
|
|
|
|
|
|
|
|
|
|
# check for cty.csv file
|
|
|
|
self.check_cty = exists(self.config['files']['cty'])
|
|
|
|
if not self.check_cty:
|
|
|
|
url = "https://www.country-files.com/bigcty/download/bigcty.zip"
|
|
|
|
print("The file " + self.config['files']['cty'] + " is missing.")
|
|
|
|
print("Trying to download " + url)
|
|
|
|
zip_name = self.download_file(url, "bigcty.zip" )
|
|
|
|
with zipfile.ZipFile(zip_name, 'r') as zip_ref:
|
|
|
|
zip_ref.extract("cty.csv")
|
|
|
|
os.remove(zip_name)
|
|
|
|
self.check_cty = exists(self.config['files']['cty'])
|
|
|
|
if self.check_cty:
|
|
|
|
print("File successfully downloaded and extracted.")
|
|
|
|
else:
|
|
|
|
print("something went wrong while downloading " + url)
|
|
|
|
|
|
|
|
# check for lotw user activity file
|
|
|
|
self.check_lotw_activity = exists(self.config['files']['lotw_activity'])
|
|
|
|
if not self.check_lotw_activity:
|
|
|
|
url = "https://lotw.arrl.org/lotw-user-activity.csv"
|
|
|
|
print("The file " + self.config['files']['lotw_activity'] + " is missing.")
|
|
|
|
print("Trying to download " + url)
|
|
|
|
file_name = self.download_file(url, self.config['files']['lotw_activity'])
|
|
|
|
self.check_lotw_activity = exists(self.config['files']['lotw_activity'])
|
|
|
|
if self.check_lotw_activity:
|
|
|
|
print("File successfully downloaded")
|
|
|
|
else:
|
|
|
|
print("something went wrong while downloading " + url)
|
|
|
|
|
|
|
|
|
2022-05-19 15:59:26 +02:00
|
|
|
|
|
|
|
def print_banner(self):
|
|
|
|
"""print an awesome banner"""
|
|
|
|
ver = self.version
|
|
|
|
# print the banner
|
|
|
|
print(fg(self.rnd_col())+" ___ _ ___ _ ")
|
|
|
|
print(fg(self.rnd_col())+" / __|___| |___ _ _/ __|_ __ ___| |_ ")
|
|
|
|
print(fg(self.rnd_col())+" | (__/ _ \ / _ \ '_\__ \ '_ \/ _ \ _|")
|
|
|
|
print(fg(self.rnd_col())+" \___\___/_\___/_| |___/ .__/\___/\__|")
|
|
|
|
print(fg(self.rnd_col())+" -= DK1MI =- |_| ")
|
2022-05-20 14:45:09 +02:00
|
|
|
print("")
|
2022-05-19 15:59:26 +02:00
|
|
|
print(attr('reset'))
|
2022-05-19 14:15:19 +02:00
|
|
|
|
2022-05-18 23:39:02 +02:00
|
|
|
@staticmethod
|
|
|
|
def read_config(config, file_name):
|
|
|
|
"""reads the configuration from the config file or
|
|
|
|
creates a default config file if none could be found"""
|
|
|
|
if os.path.isfile(file_name):
|
|
|
|
config.read(file_name)
|
|
|
|
else:
|
|
|
|
config = configparser.ConfigParser()
|
|
|
|
config['cluster'] = {
|
|
|
|
'host': 'dxc.nc7j.com',
|
|
|
|
'port': '7373',
|
2022-05-19 14:15:19 +02:00
|
|
|
'user': 'N0CALL',
|
2022-05-18 23:39:02 +02:00
|
|
|
'timeout': '100'}
|
2022-05-20 14:45:09 +02:00
|
|
|
config['files'] = {
|
|
|
|
'cty': 'cty.csv',
|
|
|
|
'lotw_confirmed': 'lotw.adi',
|
|
|
|
'lotw_activity': 'lotw-user-activity.csv'}
|
2022-05-19 17:40:17 +02:00
|
|
|
config['lotw'] = {
|
|
|
|
'user': 'N0CALL',
|
|
|
|
'password': 'XXXXXXXXX',
|
|
|
|
'mode': 'ssb'}
|
2022-05-19 14:15:19 +02:00
|
|
|
config['band_colors'] = {
|
2022-05-18 23:39:02 +02:00
|
|
|
"145": "white",
|
|
|
|
"144": "white",
|
|
|
|
"50": "white",
|
|
|
|
"29": "yellow",
|
|
|
|
"28": "yellow",
|
|
|
|
"24": "red",
|
|
|
|
"21": "orchid",
|
|
|
|
"18": "green",
|
|
|
|
"14": "steel_blue_3",
|
|
|
|
"10": "orange_1",
|
|
|
|
"7": "cyan",
|
|
|
|
"5": "white",
|
|
|
|
"3": "light_cyan",
|
|
|
|
"1": "white",
|
|
|
|
'alert_bg': 'indian_red_1a',
|
|
|
|
'alert_fg': 'white',
|
|
|
|
'default_bg': 'black'}
|
2022-05-19 14:15:19 +02:00
|
|
|
config['cont_colors'] = {
|
2022-05-19 15:59:26 +02:00
|
|
|
"AF": "light_salmon_3b",
|
2022-05-19 14:15:19 +02:00
|
|
|
"AN": "white",
|
2022-05-19 15:59:26 +02:00
|
|
|
"AS": "orange_red_1",
|
2022-05-19 14:15:19 +02:00
|
|
|
"EU": "cyan",
|
|
|
|
"NA": "steel_blue_3",
|
|
|
|
"OC": "orchid",
|
|
|
|
"SA": "light_goldenrod_2a"}
|
|
|
|
config['colors']= {
|
|
|
|
'use_colors': 'yes',
|
|
|
|
'color_by' : 'continent',
|
|
|
|
'alert_bg': 'indian_red_1a',
|
|
|
|
'alert_fg': 'white',
|
|
|
|
'default_bg': 'black'}
|
|
|
|
|
2022-05-18 23:39:02 +02:00
|
|
|
with open(file_name, 'w') as configfile:
|
|
|
|
config.write(configfile)
|
|
|
|
print("\nNo configuration file found. A new configuration file has been created.")
|
|
|
|
print("\nPlease edit the file " + file_name + " and restart the application.\n" )
|
|
|
|
sys.exit()
|
|
|
|
return config
|
|
|
|
|
2022-05-20 14:45:09 +02:00
|
|
|
def get_confirmed_entities(self):
|
2022-05-18 23:39:02 +02:00
|
|
|
ret = []
|
|
|
|
#TODO: download file and/or tell user what to do
|
2022-05-20 14:45:09 +02:00
|
|
|
file = open(self.config['files']['lotw_confirmed'], "r")
|
2022-05-18 23:39:02 +02:00
|
|
|
for row in file:
|
2022-05-20 14:45:09 +02:00
|
|
|
if re.search("<DXCC:", row):
|
|
|
|
dxcc = row.partition(">")[2].lower().rstrip()
|
|
|
|
if dxcc not in ret:
|
|
|
|
ret.append(dxcc)
|
2022-05-18 23:39:02 +02:00
|
|
|
return ret
|
|
|
|
|
2022-05-20 14:45:09 +02:00
|
|
|
def check_lotw(self, call):
|
2022-05-18 23:39:02 +02:00
|
|
|
ret = ""
|
|
|
|
#TODO: download file and/or tell user what to do
|
2022-05-20 14:45:09 +02:00
|
|
|
csv_file = csv.reader(open(self.config['files']['lotw_activity'], "r"), delimiter=",")
|
2022-05-18 23:39:02 +02:00
|
|
|
#loop through the csv file
|
|
|
|
for row in csv_file:
|
|
|
|
if call == row[0]:
|
|
|
|
ret = row[1]
|
|
|
|
return ret
|
|
|
|
return ret
|
|
|
|
|
2022-05-20 14:45:09 +02:00
|
|
|
def get_cty_row(self, call):
|
2022-05-18 23:39:02 +02:00
|
|
|
done = False
|
|
|
|
while not done:
|
2022-05-20 14:45:09 +02:00
|
|
|
for row in self.cty:
|
|
|
|
entities = row[9].replace(";", "").replace("=", "").split(" ")
|
|
|
|
# TODO: schauen ob = davor und match -> als special call anzeigen
|
|
|
|
for prefix in entities:
|
|
|
|
if call == prefix:
|
|
|
|
return row
|
2022-05-18 23:39:02 +02:00
|
|
|
call = call[:-1]
|
2022-05-19 14:15:19 +02:00
|
|
|
if call == "":
|
2022-05-20 14:45:09 +02:00
|
|
|
return ["-", "-", "-", "-", "-", "-", "-"]
|
2022-05-18 23:39:02 +02:00
|
|
|
return None
|
|
|
|
|
2022-05-20 14:45:09 +02:00
|
|
|
|
2022-05-18 23:39:02 +02:00
|
|
|
def get_spots(self):
|
|
|
|
with Telnet(self.config['cluster']['host'], int(self.config['cluster']['port']), \
|
|
|
|
int(self.config['cluster']['timeout'])) as telnet:
|
|
|
|
while True:
|
|
|
|
line_enc = telnet.read_until(b"\n") # Read one line
|
|
|
|
line = line_enc.decode('ascii')
|
|
|
|
if "enter your call" in line:
|
2022-05-19 17:40:17 +02:00
|
|
|
b_user = str.encode(self.config['cluster']['user']+"\n")
|
|
|
|
telnet.write(b_user)
|
|
|
|
elif " Hello " in line:
|
|
|
|
print(fg("grey_27") + line + attr("reset"))
|
|
|
|
foreground = "white"
|
|
|
|
background = "grey_27"
|
|
|
|
sep = fg("grey_27")+'|'+fg(foreground)
|
|
|
|
row = ["DE", sep, "Freq", sep, "DX", \
|
|
|
|
sep, "Country", sep, "C", sep, "L", sep, "Comment", sep, "Time"]
|
|
|
|
|
|
|
|
print(bg(background) + fg(foreground) + \
|
|
|
|
'{:9.9} {:<1} {:>7.7} {:<1} {:<10.10} {:<1} {:<16.16} {:<1} {:<2.2} {:<1} {:<1.1} {:<1} {:<30.30} {:<1} {:<4.4}'.format(*row) + attr("reset"))
|
|
|
|
b_cmd = str.encode("sh/dx/50 @\n")
|
|
|
|
telnet.write(b_cmd)
|
|
|
|
elif "DX de" in line or "Dx de" in line:
|
2022-05-18 23:39:02 +02:00
|
|
|
try:
|
|
|
|
band_col = ""
|
2022-05-19 17:40:17 +02:00
|
|
|
call_de = re.search('D(X|x) de (.+?): ', line).group(2)
|
2022-05-18 23:39:02 +02:00
|
|
|
freq = re.search(': +(.+?) ', line).group(1)
|
|
|
|
call_dx = re.search(freq + ' +(.+?) ', line).group(1)
|
|
|
|
time = re.search('[^ ]*$', line).group(0)[0:4]
|
|
|
|
comment = re.search(call_dx + ' +(.+?) +' + time, line).group(1)
|
2022-05-20 14:45:09 +02:00
|
|
|
|
|
|
|
if self.check_cty:
|
|
|
|
cty_details = self.get_cty_row(call_dx)
|
|
|
|
else:
|
|
|
|
cty_details = ["-","-","-","-","-","-","-","-","-","-"]
|
|
|
|
|
|
|
|
areaname = cty_details[1]
|
|
|
|
continent = cty_details[3]
|
|
|
|
|
|
|
|
if self.check_lotw_activity and self.check_lotw(call_dx):
|
2022-05-19 17:40:17 +02:00
|
|
|
lotw = "✓"
|
2022-05-20 14:45:09 +02:00
|
|
|
else:
|
|
|
|
lotw = ""
|
|
|
|
|
2022-05-18 23:39:02 +02:00
|
|
|
try:
|
2022-05-19 14:15:19 +02:00
|
|
|
if self.config['colors']['color_by'] == "band":
|
|
|
|
foreground = self.config['band_colors'][freq[:-5]]
|
|
|
|
elif self.config['colors']['color_by'] == "continent":
|
|
|
|
foreground = self.config['cont_colors'][continent]
|
|
|
|
else:
|
|
|
|
foreground = "white"
|
2022-05-18 23:39:02 +02:00
|
|
|
except Exception:
|
2022-05-19 14:15:19 +02:00
|
|
|
foreground = "white"
|
2022-05-18 23:39:02 +02:00
|
|
|
|
|
|
|
freq = freq.replace('.0', '')
|
|
|
|
|
2022-05-20 14:45:09 +02:00
|
|
|
if self.check_lotw_confirmed and cty_details[2] not in self.confirmed_entities:
|
2022-05-18 23:39:02 +02:00
|
|
|
background = self.config['colors']['alert_bg']
|
2022-05-19 14:15:19 +02:00
|
|
|
foreground = self.config['colors']['alert_fg']
|
2022-05-18 23:39:02 +02:00
|
|
|
else:
|
|
|
|
background = self.config['colors']['default_bg']
|
|
|
|
|
2022-05-19 14:15:19 +02:00
|
|
|
sep = fg("grey_27")+'|'+fg(foreground)
|
2022-05-18 23:39:02 +02:00
|
|
|
|
|
|
|
row = [call_de, sep, freq, sep, call_dx, \
|
2022-05-19 17:40:17 +02:00
|
|
|
sep, areaname, sep, continent, sep, lotw, sep, comment, sep, time]
|
2022-05-18 23:39:02 +02:00
|
|
|
|
2022-05-19 14:15:19 +02:00
|
|
|
print(bg(background) + fg(foreground) + \
|
2022-05-19 17:40:17 +02:00
|
|
|
'{:9.9} {:<1} {:>7.7} {:<1} {:<10.10} {:<1} {:<16.16} {:<1} {:<2.2} {:<1} {:<1.1} {:<1} {:<30.30} {:<1} {:<4.4}'.format(*row) + attr("reset"))
|
2022-05-18 23:39:02 +02:00
|
|
|
|
|
|
|
except AttributeError:
|
|
|
|
print(line)
|
|
|
|
|
|
|
|
#####################################################
|
|
|
|
# Main Routine #
|
|
|
|
#####################################################
|
|
|
|
def main():
|
2022-05-20 14:45:09 +02:00
|
|
|
try:
|
|
|
|
color_spot = ColorSpot()
|
|
|
|
color_spot.get_spots()
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
sys.exit(0) # or 1, or whatever
|
2022-05-18 23:39:02 +02:00
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
try:
|
|
|
|
sys.exit(main())
|
|
|
|
except EOFError:
|
|
|
|
pass
|
2022-05-19 14:15:19 +02:00
|
|
|
|
|
|
|
|
|
|
|
|