def __init__(self, state): self.state = state self.cal_check = CalendarCheck(self.state) self.config = ConfigControls() '''Config''' self.c = self.get_config() self.RING_TIME_THRESHOLD = 15 #[seconds]
def __init__(self, bot): '''Config''' self.max_msg_line_length = 29 self.max_reply_line_length = 28 '''Initializations''' self.config = ConfigControls() self.mopidy = MopidyControls(self) self.mopidy.refresh_token() self.alarm = AlarmControls(self) self.cal_check = CalendarCheck(self) self.font = AsciiFont() self.bot = bot self.default_volume = self.get_default_control("volume") self.mopidy.set_volume_sys(self.default_volume) self.set_reply_text('') '''General variables''' self.state = 'music' self.dashboard_type = 'music' self.keyboard_type = 'home home' self.music_status = self.mopidy.get_status() self.last_dash_msg = [False] self.last_reply_msg = [False] self.chat_id_list = [] self.chat_password = self.get_password() self.auto_refresh_enabled = True self.week_events = [[], [], [], [], [], [], []] self.set_alarm_day = 0 self.snooze_counts = 0 self.set_config_name = ' ' self.alarm_to_edit = [['manual', 'disabled'], datetime.time(hour=0, minute=0)] '''Search stored variables''' self.search_type = False self.search_query = False self.options_list = False self.uri_list = False self.uri = False
class AlarmControls: def __init__(self, state): self.state = state self.cal_check = CalendarCheck(self.state) self.config = ConfigControls() '''Config''' self.c = self.get_config() self.RING_TIME_THRESHOLD = 15 #[seconds] def ring(self): '''Plays the alarm music''' self.c = self.get_config( ) # probably is better to refresh config in main while self.state.auto_refresh_enabled: self.state.auto_refresh_enabled = False self.state.music_status.mopidy_playing = True self.state.mopidy.clear() self.state.mopidy.repeat('on') self.state.mopidy.random('on') if not self.state.mopidy.load(self.c.ring_music, 'uri'): logger.warning('Local file loaded for ringing') self.state.mopidy.load_local_song() self.state.mopidy.shuffle() self.state.mopidy.play() self.state.mopidy.set_volume_sys(int(self.c.ring_start_volume)) self.state('ringing') self.state.keyboard_type = 'ringing' self.state.refresh_dashboard() def set_ring_volume(self, ringing_time): '''Sets the ring volume according to two linear relationships, one for the first half volume augment and the other until the maximum ringing volume''' if ringing_time <= self.c.half_ring_volume_sec: vol_slope = (( (self.c.max_ringing_volume - self.c.ring_start_volume) / 2) / self.c.half_ring_volume_sec) volume = int(self.c.ring_start_volume + vol_slope * ringing_time) elif ringing_time <= self.c.max_ring_volume_sec: vol_slope = ( ((self.c.max_ringing_volume - self.c.ring_start_volume) / 2) / (self.c.max_ring_volume_sec - self.c.half_ring_volume_sec)) volume = int( (self.c.max_ringing_volume + self.c.ring_start_volume) / 2 + vol_slope * (ringing_time - self.c.half_ring_volume_sec)) else: volume = int(self.c.max_ringing_volume) volume = min(volume, int(self.c.max_ringing_volume)) self.state.mopidy.set_volume_sys(volume) def get_alarms(self): '''Gets the alarm string array from the saved file''' file = open(os.path.join(self.config.path, "saved_alarms.py"), "rb") alarms = pickle.load(file) file.close() return alarms def get_config(self): try: text_config = self.config.get_section('Alarm Controls') config_name_list = [] for line in text_config: config_name_list.append( line.split('=')[0].split('(')[0].strip()) config_list = namedtuple('config_list', ' '.join(config_name_list)) for line in text_config: for config_name in config_list._fields: if config_name in line: config_value = line.split(' = ')[1].strip() if config_value.isdigit(): config_value = int(config_value) setattr(config_list, config_name, config_value) return config_list except: # Put here the default control values logger.error( "Bad AlarmControls config, default config applied: {}".format( sys._getframe().f_code.co_name)) return config_list( 10, 30, 70, 14, 35, 30, 60, 10, 6, 10, "I am awake", 'spotify:user:2155eprgg73n7x46ybpzpycra:playlist:64xSAgfPBl8HjIKJBi3CIi' ) def get_config_units(self, config_name): if 'time' in config_name or 'min' in config_name: units = '(minutes)' elif 'sec' in config_name: units = '(seconds)' elif 'volume' in config_name: units = '(%)' elif 'counts' in config_name: units = '(-)' else: units = '' return units def set_alarms(self, alarms): '''Saves the alarm string array to the file''' file = open(os.path.join(self.config.path, "saved_alarms.py"), "wb") pickle.dump(alarms, file) file.close() return def set_config(self, config_name, config_value): '''Saves the config parameter in the text file and checks the value''' if 'ring_music' in config_name: if config_value.isdigit(): return 'Bad URI format' elif 'alarm_stop_password' in config_name: if len(config_value) > 100: return "Don't be silly" elif '/' in config_value: return 'invalid key "/"' else: if not config_value.strip().isdigit(): return 'Must be a positive number' else: if ('snooze_min' in config_name or 'max_snooze_counts' in config_name): if int(config_value) == 0: return 'Must be greater than 0' if 'snooze_min' in config_name: if int(config_value) > 30: return 'Snooze up to 30 min' elif 'max_snooze_counts' in config_name: if int(config_value) > 1000: return "Don't be silly" elif ('ring_start_volume' in config_name or 'max_ringing_volume' in config_name): if int(config_value) > 100: return 'Up to 100%' if 'ring_start_volume' in config_name: if int(config_value) > int(self.c.max_ringing_volume): return "Start Vol(<)Max Vol" elif 'max_ringing_volume' in config_name: if int(config_value) < int(self.c.ring_start_volume): return "Max Vol(>)Start Vol" elif 'max_ringing_time' in config_name: if int(config_value) < 5 or int(config_value) > 60: return 'Between 5 and 60' elif 'event_time_before' in config_name: #first_event_time_before and other_event_time_before if int(config_value) > 360: return 'Up to 6 hours before' elif 'ring_volume_sec' in config_name: #max_ring_volume_time and half_ring_volume_time if int(config_value) > 180: return 'Up to 3 minutes' elif 'max' in config_name: if int(config_value) < int( self.c.half_ring_volume_sec): return 'Max Time(>)Half Time' elif 'half' in config_name: if int(config_value) > int(self.c.max_ring_volume_sec): return 'Half Time(<)Max Time' elif 'ring_refresh_sec' in config_name: if int(config_value) > 60 or int(config_value < 5): return 'Betweeen 5 and 60 seconds' else: return 'Config name not recognized!' unit = self.get_config_units(config_name) if unit == '': setattr(self.c, config_name, str(config_value)) else: setattr(self.c, config_name, int(config_value)) config_list = [] for name in self.c._fields: value = getattr(self.c, name) units = self.get_config_units(name) if isinstance(value, int): value = str(value) config_list.append(name + ' ' + units + ' = ' + value) self.config.set_section('Alarm Controls', config_list) return 'Your wishes are orders' def is_config(self, config_name): '''Says if a name is a configuration name''' for name in self.c._fields: if config_name in name: return True return False def config_options_list(self, config_name): options_list = [] if 'ring_music' in config_name: return False else: if 'snooze_min' in config_name: options_list = list(range(5, 35, 5)) elif 'max_snooze_counts' in config_name: options_list = list(range(1, 11, 1)) elif 'ring_start_volume' in config_name: options_list = list(range(0, int(self.c.max_ringing_volume), 5)) elif 'max_ringing_volume' in config_name: options_list = list( range(int(self.c.ring_start_volume), 105, 5)) elif 'max_ringing_time' in config_name: options_list = list(range(5, 35, 5)) elif 'event_time_before' in config_name: #first_event_time_before and other_event_time_before options_list = list(range(0, 135, 15)) elif 'alarm_stop_password' in config_name: options_list = ["I am awake"] elif 'half_ring_volume_sec' in config_name: options_list = list( range(0, int(self.c.max_ring_volume_sec), 2)) elif 'max_ring_volume_sec' in config_name: options_list = list( range(int(self.c.half_ring_volume_sec), 60, 5)) elif 'ring_refresh_sec' in config_name: options_list = list(range(10, 70, 10)) else: return False return options_list def temporizer_options_list(self): temp_list = list(range(5, 30, 5)) temp_list.extend(list(range(30, 75, 15))) return temp_list def alarm_str2time(self, alarm_str): ''' Format the alarm string to time ''' separator = alarm_str.find('-') if separator >= 0: alarm_type = alarm_str[:separator] alarm_type = alarm_type.split(' ') alarm_time = alarm_str[(separator + 1):] alarm_time = datetime.datetime.strptime(alarm_time, '%c') return [alarm_type, alarm_time] else: logging.error("Bad alarm format: {} ".format( sys._getframe().f_code.co_name)) return [False, False] def alarm_time2str(self, alarm_type, alarm_time): ''' Format the alarm time to string ''' alarm_type_str = ' '.join(alarm_type) alarm_time_str = alarm_time.strftime('%c') alarm_str = '-'.join([alarm_type_str, alarm_time_str]) return alarm_str def to_localtime(self, d): '''Localtime defined by the raspberry localtime''' utcoffset = datetime.datetime.now( datetime.timezone.utc).astimezone().tzinfo.utcoffset(None) if d is not None: return d + utcoffset else: return None def to_utc(self, d): utcoffset = datetime.datetime.now( datetime.timezone.utc).astimezone().tzinfo.utcoffset(None) if d is not None: return d - utcoffset else: return None def set_auto_alarms(self, week_events): '''Sets the alarms acording to the first event of the day''' alarms = self.get_alarms() now_local = self.to_localtime(datetime.datetime.utcnow()) today = now_local.date() new_alarms = [] first_event_time_before = datetime.timedelta( minutes=int(self.c.first_event_time_before)) auto_alarm_disabled = [False, False, False, False, False, False, False] for alarm in alarms: [alarm_type, alarm_time] = self.alarm_str2time(alarm) if alarm_time.date() - today >= datetime.timedelta(0): if 'auto' in alarm_type[0]: if 'enabled' in alarm_type[1]: pass # We delete enabled auto alarms and update them later else: #We mark the disabled auto alarms to chekc update days_to_go = (self.to_localtime(alarm_time).weekday() - today.weekday()) if days_to_go < 0: days_to_go += 7 auto_alarm_disabled[days_to_go] = alarm_time else: # We keep all manual alarms not outdated new_alarm = self.alarm_time2str(alarm_type, alarm_time) new_alarms.append(new_alarm) else: pass #if the alarm is outdated we delete it first_events_localtime = self.cal_check.get_day_first_events_localtime( week_events) first_events = [self.to_utc(item) for item in first_events_localtime] for day, event in enumerate(first_events): if event is not None: # if there is an event we update the alarm if auto_alarm_disabled[day]: if event - first_event_time_before < auto_alarm_disabled[ day]: # if the event is earlier than the diabled event we enable it new_alarm = self.alarm_time2str( ['auto', 'enabled'], event - first_event_time_before) new_alarms.append(new_alarm) else: # if the event is later than the disabled event we keep it disabled new_alarm = self.alarm_time2str( ['auto', 'disabled'], event - first_event_time_before) new_alarms.append(new_alarm) else: # if the event was enabled or the event is new we keep it enabled new_alarm = self.alarm_time2str( ['auto', 'enabled'], event - first_event_time_before) new_alarms.append(new_alarm) else: #if there is no event now we delete the auto alarm pass new_alarms = list(set(new_alarms)) #This removes the duplicates self.set_alarms(new_alarms) return True def build_temporizer_timedelta(self, temp_minutes): now = datetime.datetime.utcnow() return datetime.timedelta(0, hours=now.hour, minutes=now.minute + temp_minutes) def set_manual_alarm(self, set_datetime): '''Sets a manual alarm some days after at a desired time set_datetime = datetime.timedelta(day_increase,set_time) ''' today = datetime.datetime.utcnow().date() new_alarm = (datetime.datetime.combine(today, datetime.time(0)) + set_datetime) repeated = False alarms = self.get_alarms() for alarm in alarms: [alarm_type, alarm_time] = self.alarm_str2time(alarm) if abs((alarm_time - new_alarm).total_seconds()) <= 60: repeated = True if not repeated: new_alarm_str = self.alarm_time2str(['manual', 'enabled'], new_alarm) alarms.append(new_alarm_str) self.set_alarms(alarms) return else: return def set_manual_alarm_localtime(self, set_datetime): '''Sets a manual alarm some days after at a desired time on the local timezone set_datetime = datetime.timedelta(day_increase,set_time) ''' utcoffset = datetime.datetime.now( datetime.timezone.utc).astimezone().tzinfo.utcoffset(None) today = (datetime.datetime.utcnow() + utcoffset).date() new_alarm = (datetime.datetime.combine(today, datetime.time(0)) + set_datetime - utcoffset) repeated = False alarms = self.get_alarms() for alarm in alarms: [alarm_type, alarm_time] = self.alarm_str2time(alarm) if abs((alarm_time - new_alarm).total_seconds()) <= 60: repeated = True if not repeated: new_alarm_str = self.alarm_time2str(['manual', 'enabled'], new_alarm) alarms.append(new_alarm_str) self.set_alarms(alarms) return else: return def alarm_check(self): '''Checks if an alarm time has been reached and rings acordingly''' alarms = self.get_alarms() now = datetime.datetime.utcnow() min_time_to_ring = 24 * 60 * 60 for alarm in alarms: [alarm_type, alarm_time] = self.alarm_str2time(alarm) ''' Check only enabled alarms ''' if 'enabled' in alarm_type[1]: time_to_ring = (alarm_time - now).total_seconds() if (time_to_ring <= self.RING_TIME_THRESHOLD and (now - alarm_time).total_seconds() <= 20 * 60): self.ring() return 0 if time_to_ring < 0: time_to_ring = min_time_to_ring min_time_to_ring = min(min_time_to_ring, time_to_ring) return min_time_to_ring def edit_closest_alarm(self, action): '''Edits the closest alarm acording to the input action, useful for disable the alarm that is ringing now or activate snooze mode ''' alarms = self.get_alarms() now = datetime.datetime.utcnow() success = False for index, alarm in enumerate(alarms): [alarm_type, alarm_time] = self.alarm_str2time(alarm) ''' Check only enabled alarms ''' if 'enabled' in alarm_type[1]: if 'disable' in action: if ((alarm_time - now).total_seconds() <= 11 * 60 and (now - alarm_time).total_seconds() <= 20 * 60): alarm_type[1] = 'disabled' success = True logger.info('Alarm disabled') elif 'snooze' in action: if ((alarm_time - now).total_seconds() <= 60 and (now - alarm_time).total_seconds() <= 20 * 60): if 'manual' in alarm_type[0]: alarm_time = alarm_time + datetime.timedelta( minutes=self.c.snooze_min) logger.info('Manual alarm snooze') else: # Disable auto alarm and set manual alarm alarm_type[1] = 'disabled' new_alarm_time = alarm_time + datetime.timedelta( minutes=self.c.snooze_min) new_alarm_type = ['manual', 'enabled'] alarms.append( self.alarm_time2str(new_alarm_type, new_alarm_time)) logger.info('Auto alarm snooze') success = True alarms[index] = self.alarm_time2str(alarm_type, alarm_time) self.set_alarms(alarms) return success def edit_alarm(self, days_to_go, alarm_time, action, change_time=None): '''Edits an alarm configuration acording to the input action''' today = self.to_localtime(datetime.datetime.utcnow()) edit_alarm_time = datetime.datetime.combine( today + datetime.timedelta(days=days_to_go), alarm_time) alarms = self.get_alarms() new_alarms = [] success = False for alarm in alarms: [alarm_type, alarm_time] = self.alarm_str2time(alarm) if abs((alarm_time - edit_alarm_time).total_seconds()) < 60: if 'toggle' in action: if 'disabled' in alarm_type[1]: alarm_type[1] = 'enabled' else: alarm_type[1] = 'disabled' elif 'change' in action: if change_time is None: logger.error("Bad imput: {}".format( sys._getframe().f_code.co_name)) success = False return success else: if 'manual' in alarm_type[0]: alarm_time = datetime.datetime.combine( today + datetime.timedelta(days=days_to_go), change_time) else: #When changing auto alarms, disable auto and create new manual alarm_type[1] = 'disabled' new_alarm_time = datetime.datetime.combine( today + datetime.timedelta(days=days_to_go), change_time) new_alarms.append( self.alarm_time2str(['manual', 'enabled'], new_alarm_time)) if not 'delete' in action: new_alarms.append( self.alarm_time2str(alarm_type, alarm_time)) success = True else: new_alarms.append(self.alarm_time2str(alarm_type, alarm_time)) self.set_alarms(new_alarms) return success def get_week_alarms_localtime(self): '''Gets a list of alarms ordered by day, and time, today alarms are on week_alarms[0], today first alarm is on week_alarms[0][0], tomorrow is on week_alarms[1], etc. ''' today = self.to_localtime(datetime.datetime.utcnow()) alarms = self.get_alarms() week_alarms = [[], [], [], [], [], [], []] for alarm in alarms: [alarm_type, alarm_time] = self.alarm_str2time(alarm) alarm_time = self.to_localtime(alarm_time) '''Check within a week''' if ((alarm_time.date() - today.date() >= datetime.timedelta(0)) and (alarm_time.date() - today.date() < datetime.timedelta(7))): weekday = alarm_time.weekday() week_alarms[weekday].append([alarm_type, alarm_time.time()]) sorted_alarms = [[], [], [], [], [], [], []] for weekday, day in enumerate(week_alarms): sorted_alarms[weekday] = sorted(day, key=lambda day: day[1]) week_alarms = numpy.roll(sorted_alarms, -today.weekday()).tolist() return week_alarms def build_msg_text_from_alarms(self): '''Builds a msg_txt from the alarms set on the week in a very specific format, shows also the alarm type ''' today = self.to_localtime(datetime.datetime.utcnow()) today_weekday = today.weekday() week_alarms = self.get_week_alarms_localtime() _type = 0 _time = 1 max_length = 0 for day in week_alarms: if len(day) > max_length: max_length = len(day) alarm_list = [] for x in range(0, max_length): hours_list = [] minut_list = [] type_list = [] for day in week_alarms: if x < len(day): if 'disabled' in day[x][_type][1]: hours_list.append(day[x][_time].strftime("x%H|")) else: hours_list.append(day[x][_time].strftime(" %H|")) minut_list.append(day[x][_time].strftime(":%M|")) if 'auto' in day[x][_type][0]: type_list.append('-A-|') else: type_list.append('---|') else: hours_list.append(' |') minut_list.append(' |') type_list.append('---|') hours_str = ''.join(hours_list) minut_str = ''.join(minut_list) type_str = ''.join(type_list) alarm_list.append('`' + type_str.strip('|') + '`') alarm_list.append('`' + hours_str.strip('|') + '`') alarm_list.append('`' + minut_str.strip('|') + '`') num_list = [] month_list = [] for x in range(0, 7): day = today + datetime.timedelta(x) month_list.append(day.strftime('%B')[:3] + '|') num_list.append(day.strftime(' %d|')) name_list = ['Mon|', 'Tue|', 'Wed|', 'Thu|', 'Fri|', 'Sat|', 'Sun|'] name_list = numpy.roll(name_list, -today_weekday).tolist() msg_text = ''.join([ '`' + ''.join(name_list).strip('|') + '`' + '\n', '\n'.join(alarm_list) + '\n', '`' + ''.join(month_list).strip('|') + '`' + '\n', '`' + ''.join(num_list).strip('|') + '`' ]) t = self.alarm_check() hours = str(int(t / 3600) % 24) if len(hours) < 2: hours = '0' + hours minutes = str(int(t / 60) % 60) if len(minutes) < 2: minutes = '0' + minutes last_line = ('`--`' + u'\U0001F567' + u'\U0001F552' + u'\U000023F3' + u'\U000027A1' + '` `' + hours[0] + u'\U000020E3' + hours[1] + u'\U000020E3' + '*:*' + minutes[0] + u'\U000020E3' + minutes[1] + u'\U000020E3' + u'\U0001F555' + u'\U0001F55B' + '`--`') msg_text = '\n'.join([msg_text, last_line]) return msg_text
class State(object): def __init__(self, bot): '''Config''' self.max_msg_line_length = 29 self.max_reply_line_length = 28 '''Initializations''' self.config = ConfigControls() self.mopidy = MopidyControls(self) self.mopidy.refresh_token() self.alarm = AlarmControls(self) self.cal_check = CalendarCheck(self) self.font = AsciiFont() self.bot = bot self.default_volume = self.get_default_control("volume") self.mopidy.set_volume_sys(self.default_volume) self.set_reply_text('') '''General variables''' self.state = 'music' self.dashboard_type = 'music' self.keyboard_type = 'home home' self.music_status = self.mopidy.get_status() self.last_dash_msg = [False] self.last_reply_msg = [False] self.chat_id_list = [] self.chat_password = self.get_password() self.auto_refresh_enabled = True self.week_events = [[], [], [], [], [], [], []] self.set_alarm_day = 0 self.snooze_counts = 0 self.set_config_name = ' ' self.alarm_to_edit = [['manual', 'disabled'], datetime.time(hour=0, minute=0)] '''Search stored variables''' self.search_type = False self.search_query = False self.options_list = False self.uri_list = False self.uri = False def __call__(self, state=None): if state is None: return self.state else: self.state = state logger.info('State changed to ' + state) def get_password(self): '''Returns the password from the config file''' password = self.config.get_section('Password') if len(password) > 1: logger.warning('Only first chat password is accepted') return password[0].strip(' ') def get_default_control(self, control): '''Returns the desired default control from the config file''' text = self.config.get_section('Default Controls') for line in text: if control in line: default_control = line.split(" = ")[1] if default_control.isdigit(): default_control = int(default_control) return default_control return False def set_default_control(self, control, value): '''Changesthe desired default control in the config file''' text = self.config.get_section('Default Controls') new_text = [] for line in text: if control in line: new_text.append(control + ' = ' + str(value)) else: new_text.append(line) self.config.set_section('Default Controls', new_text) return True def dashboard(self): '''Selects a dashboard to output''' dashboard_type = self.dashboard_type if 'music' in dashboard_type: status = self.music_status if status.random: random_indicator = u'\U00002714 \U0001F500' else: random_indicator = u'\U0000274C \U0001F500' if status.repeat: repeat_indicator = u'\U00002714 \U0001F501' else: repeat_indicator = u'\U0000274C \U0001F501' if status.playing: play_indicator = u'\U000023F8' else: play_indicator = u'\U000025B6' keyboard = InlineKeyboardMarkup(inline_keyboard=[ [ InlineKeyboardButton(text=u'\U000023EE', callback_data='/prev'), InlineKeyboardButton(text=play_indicator, callback_data='/play'), InlineKeyboardButton(text=u'\U000023ED', callback_data='/next') ], [ InlineKeyboardButton(text=u'\U000023EA', callback_data='/step_prev'), InlineKeyboardButton(text=u'\U000023F9', callback_data='/stop'), InlineKeyboardButton(text=u'\U000023E9', callback_data='/step_next') ], [ InlineKeyboardButton(text=random_indicator, callback_data='/random_mode'), InlineKeyboardButton(text=u'\U0001F508 \U0001F53B', callback_data='/volume_down'), InlineKeyboardButton(text=u'\U0001F50A \U0001F53A', callback_data='/volume_up'), InlineKeyboardButton(text=repeat_indicator, callback_data='/repeat_mode') ], ]) elif 'ring' in dashboard_type: keyboard = InlineKeyboardMarkup(inline_keyboard=[ [ InlineKeyboardButton(text=u'\U0001F634' + '/snooze' + u'\U0001F634', callback_data='/snooze') ], ]) elif 'snooze' in dashboard_type: keyboard = InlineKeyboardMarkup(inline_keyboard=[ [ InlineKeyboardButton(text='refresh time counter', callback_data='/time') ], ]) elif 'alarm view' in dashboard_type: keyboard = InlineKeyboardMarkup(inline_keyboard=[ [ InlineKeyboardButton(text=u'\U0001F527', callback_data='/set_alarm:0'), InlineKeyboardButton(text=u'\U0001F527', callback_data='/set_alarm:1'), InlineKeyboardButton(text=u'\U0001F527', callback_data='/set_alarm:2'), InlineKeyboardButton(text=u'\U0001F527', callback_data='/set_alarm:3'), InlineKeyboardButton(text=u'\U0001F527', callback_data='/set_alarm:4'), InlineKeyboardButton(text=u'\U0001F527', callback_data='/set_alarm:5'), InlineKeyboardButton(text=u'\U0001F527', callback_data='/set_alarm:6'), InlineKeyboardButton(text=u'\U00002699', callback_data='/change_config'), ], ]) elif 'alarm config' in dashboard_type: inline_keyboard_array = [] config_list = self.alarm.get_config() for name in config_list._fields: if not 'ring_music' in name: value = getattr(config_list, name) units = self.alarm.get_config_units(name) if isinstance(value, int): value = str(value) button_text = u'\U00002699' + name + ' = ' + value + units inline_keyboard_array.append([ InlineKeyboardButton(text=button_text, callback_data=name) ]) keyboard = InlineKeyboardMarkup( inline_keyboard=inline_keyboard_array) elif 'general_config' in dashboard_type: keyboard = InlineKeyboardMarkup(inline_keyboard=[ [ InlineKeyboardButton(text='Password', callback_data='set_password') ], [ InlineKeyboardButton(text='Add calendar', callback_data='add_calendard') ], [ InlineKeyboardButton(text='Remove calendar', callback_data='remove_calendar') ], [ InlineKeyboardButton(text='Default Volume = ' + str(self.default_volume), callback_data='default_volume') ], ]) else: keyboard = False return keyboard def keyboard(self): '''Selects a reply keyboard to output''' keyboard_type = self.keyboard_type if 'home home' in keyboard_type: keyboard = ReplyKeyboardMarkup( keyboard=[ [KeyboardButton(text='menu')], ], resize_keyboard=True, #on_time_keyboard = True, (not supported by current telepot version) ) elif 'home menu' in keyboard_type: keyboard = ReplyKeyboardMarkup( keyboard=[ [KeyboardButton(text=u'\U000023EF' + 'music')], [KeyboardButton(text=u'\U000023F0' + 'alarm')], [KeyboardButton(text=u'\U0001F529' + 'configuration')], [KeyboardButton(text=u'\U0001F519' + '/return')], ], resize_keyboard=True, ) elif 'music' in keyboard_type: keyboard = ReplyKeyboardMarkup( keyboard=[ [ KeyboardButton(text=u'\U0001F3A7 \U0001F4DD' + 'playlists') ], [KeyboardButton(text=u'\U0001F50E' + 'search')], [KeyboardButton(text=u'\U0001F534' + 'youtube help')], [ KeyboardButton(text=u'\U0001F4FB' + 'spotify device not found?') ], [KeyboardButton(text=u'\U0001F519' + '/return')], ], resize_keyboard=True, ) elif 'search type' in keyboard_type: keyboard = ReplyKeyboardMarkup( keyboard=[ [KeyboardButton(text=u'\U0000270F \U0001F3B5' + 'track')], [KeyboardButton(text=u'\U0001F919 \U0001F399' + 'artist')], [KeyboardButton(text=u'\U0001F3B6 \U0001F4BF' + 'album')], [ KeyboardButton(text=u'\U0001F3A7 \U0001F4DD' + 'playlist') ], [KeyboardButton(text=u'\U0001F519' + '/return')], ], resize_keyboard=True, ) elif 'options list' in keyboard_type: if self.options_list: keyboard_buttons_list = [] for option in self.options_list: keyboard_buttons_list.append([KeyboardButton(text=option)]) keyboard_buttons_list.append( [KeyboardButton(text=u'\U0001F519' + '/return')]) keyboard = ReplyKeyboardMarkup( keyboard=keyboard_buttons_list, resize_keyboard=False, ) else: logger.warning('Options_list is empty') keyboard = ReplyKeyboardMarkup( keyboard=[ [KeyboardButton(text=u'\U0001F519' + '/return')], ], resize_keyboard=True, ) elif 'action' in keyboard_type: if self.uri: action_list = [u'\U000023E9' + 'play', u'\U000023CF' + 'add'] if ('artist' in self.uri or 'playlist' in self.uri or 'album' in self.uri): action_list.append(u'\U0001F500' + 'shuffle') action_list.append(u'\U0000270F \U0001F3B5' + 'tracks') if 'artist' in self.uri: action_list.append(u'\U0001F3B6 \U0001F4BF' + 'albums') if 'playlist' in self.uri: action_list.append(u'\U0001F634' + 'wake up sound' + u'\U0001F918') keyboard_buttons_list = [] for option in action_list: keyboard_buttons_list.append([KeyboardButton(text=option)]) keyboard_buttons_list.append( [KeyboardButton(text=u'\U0001F519' + '/return')]) keyboard = ReplyKeyboardMarkup( keyboard=keyboard_buttons_list, resize_keyboard=False, ) else: keyboard = ReplyKeyboardMarkup( keyboard=[ [KeyboardButton(text=u'\U0001F519' + '/return')], ], resize_keyboard=True, ) elif 'alarm menu' in keyboard_type: keyboard = ReplyKeyboardMarkup( keyboard=[ [KeyboardButton(text=u'\U000023F1' + 'set_alarm')], [KeyboardButton(text=u'\U000023F3' + 'temporizer')], [ KeyboardButton(text=u'\U0001F501' + 'refresh auto alarms') ], [KeyboardButton(text=u'\U0001F519' + '/return')], ], resize_keyboard=True, ) elif 'alarm set on_day' in keyboard_type: keyboard_buttons_list = [] for event in self.week_events[self.set_alarm_day]: event = (event - datetime.timedelta( minutes=int(self.alarm.c.other_event_time_before))) keyboard_buttons_list.append([ KeyboardButton(text=event.strftime('set ' + u'\U0001F5D3' + u'\U0001F519' + "-%H:%M")) ]) week_alarms = self.alarm.get_week_alarms_localtime() _type = 0 _time = 1 for alarm_info in week_alarms[self.set_alarm_day]: if 'auto' in alarm_info[_type][0]: a_type0 = u'\U0001F3E7 ' else: a_type0 = u'\U000024C2 ' if 'disabled' in alarm_info[_type][1]: a_type1 = u'\U0001F4F4 ' else: a_type1 = u'\U0001F51B ' keyboard_buttons_list.append([ KeyboardButton( text=alarm_info[_time].strftime("edit " + a_type0 + a_type1 + u'\U000023F0' + "-%H:%M")) ]) keyboard_buttons_list.append([ KeyboardButton(text=u'\U000023F1' + 'add new' + u'\U0001F195') ]) keyboard_buttons_list.append( [KeyboardButton(text=u'\U0001F519' + '/return')]) keyboard = ReplyKeyboardMarkup( keyboard=keyboard_buttons_list, resize_keyboard=False, ) elif 'alarm edit' in keyboard_type: if 'enabled' in self.alarm_to_edit[0][1]: toggle_text = u'\U0001F4F4' + 'disable' else: toggle_text = u'\U0001F51B' + 'enable' keyboard = ReplyKeyboardMarkup( keyboard=[ [KeyboardButton(text=toggle_text)], [KeyboardButton(text=u'\U0001F527' + 'change')], [KeyboardButton(text=u'\U00002620' + 'delete')], [KeyboardButton(text=u'\U0001F519' + '/return')], ], resize_keyboard=True, ) elif 'ringing' in keyboard_type: keyboard = ReplyKeyboardMarkup( keyboard=[ [ KeyboardButton(text=u'\U0001F634' + '/snooze' + u'\U0001F634') ], ], resize_keyboard=True, ) elif 'return' in keyboard_type: keyboard = ReplyKeyboardMarkup( keyboard=[ [KeyboardButton(text=u'\U0001F519' + '/return')], ], resize_keyboard=True, ) elif 'nothing' in keyboard_type: keyboard = ReplyKeyboardRemove(remove_keyboard=True) else: keyboard = False return keyboard def refresh_dashboard(self): '''Deletes last message and sends a new one''' msg_text = self.get_msg_text() tries = 0 chat_count = 0 for chat_id in self.chat_id_list: '''Sends the message with dashboard and the message with custom keyboard to every allowed chat id ''' while tries < 2: try: dashboard = self.dashboard() keyboard = self.keyboard() dash_msg_id = self.bot.sendMessage(chat_id, msg_text, parse_mode='Markdown', reply_markup=dashboard) reply_msg_id = self.bot.sendMessage(chat_id, self.reply_text, parse_mode='Markdown', reply_markup=keyboard) self.delete_last_msg(self.bot, chat_count) self.set_reply_text('') self.store_dash_msg( chat_count, telepot.message_identifier(dash_msg_id)) self.store_reply_msg( chat_count, telepot.message_identifier(reply_msg_id)) logger.info('Refresh') break except Exception as error: tries = tries + 1 logger.error("Could not refresh: {}\n {}".format( sys._getframe().f_code.co_name, error)) chat_count += 1 return def get_msg_text(self): if 'music' in self.state: self.music_status = self.mopidy.get_status() status = self.music_status if status.playing: playing_indicator = u'\U0001F918' else: playing_indicator = u'\U0000270B' if len(status.song) > self.max_msg_line_length: status.song = ''.join( [status.song[:self.max_msg_line_length - 3], '...']) if len(status.artist) > self.max_msg_line_length: status.artist = ''.join( [status.artist[:self.max_msg_line_length - 2], '...']) msg_text = ''.join([ '`' + (self.max_msg_line_length - 2) * '-' + '`' + '\n', playing_indicator, '*', status.song, '*', playing_indicator, '\n', u'\U0001F3A4', '_', status.artist, '_', u'\U0001F3B8', '\n', u'\U0001F3B6', '`', status.song_number, '` ', u'\U000023F3', '_', status.song_time, '_ ', u'\U0001F509', '`', status.volume, '`' ]) self.dashboard_type = 'music' elif 'alarm' in self.state: if 'change_config' in self.state: text = 'Alarm Configuration' msg_text = ( '`' + int( (self.max_reply_line_length - len(text)) / 2) * '-' + text + int( (self.max_reply_line_length + 1 - len(text)) / 2) * '-' + '`') self.dashboard_type = 'alarm config' else: msg_text = self.alarm.build_msg_text_from_alarms() self.dashboard_type = 'alarm view' elif 'ringing' in self.state: now = self.alarm.to_localtime(datetime.datetime.utcnow()) hour = now.strftime("%H") minu = now.strftime("%M") msg_list = [] hour0 = self.font(int(hour[0])) hour1 = self.font(int(hour[1])) for index in range(0, len(hour0)): msg_list.append('`' + ' ' + hour0[index] + ' ' + hour1[index] + '`') msg_list.append(' ') minu0 = self.font(int(minu[0])) minu1 = self.font(int(minu[1])) point = [' ', ' ', '# ', ' ', '# ', ' ', ' '] for index in range(0, len(minu0)): msg_list.append('`' + point[index] + minu0[index] + ' ' + minu1[index] + '`') msg_text = '\n'.join(msg_list) self.dashboard_type = 'ring' elif 'snooze' in self.state: t = self.alarm.alarm_check() seconds = str(int(t % 60)) if len(seconds) < 2: seconds = '0' + seconds minutes = str(int(t / 60) % 60) if len(minutes) < 2: minutes = '0' + minutes msg_text = (u'\U0001F634' + u'\U0001F634' + u'\U000023F3' + u'\U000027A1' + '` `' + minutes[0] + u'\U000020E3' + minutes[1] + u'\U000020E3' + '*:*' + seconds[0] + u'\U000020E3' + seconds[1] + u'\U000020E3' + u'\U0001F634' + u'\U0001F634') self.dashboard_type = 'snooze' elif 'general_config' in self.state: text = 'General Configuration' msg_text = ( '`' + int((self.max_reply_line_length - len(text)) / 2) * '-' + text + int( (self.max_reply_line_length + 1 - len(text)) / 2) * '-' + '`') self.dashboard_type = 'general_config' else: msg_text = '***WIP***' self.dashboard_type = ' ' return msg_text def set_reply_text(self, text=None, dash_join=True): if text is None: return self.reply_text else: if dash_join: text = '-'.join(text.strip(' ').split(' ')) else: text = ' '.join(text.strip(' ').split(' ')) self.reply_text = ( '`' + int((self.max_reply_line_length - len(text)) / 2) * '-' + text + int( (self.max_reply_line_length + 1 - len(text)) / 2) * '-' + '`') def auto_refresh_dashboard(self): ''' Process performed in parallel with the 'mpc idle' command that waits until something changes in the music player ''' while True: try: subprocess.check_output(['mpc', 'idle']) if self.auto_refresh_enabled: logger.info('Auto') self.refresh_dashboard() else: logger.info('Auto not enabled') except Exception as error: logger.warning('Mopidy not enabled \n' + error) self.send_chat_action('upload_audio') sleep(2) # HUGE BLOCK def auto_refresh(self, enabled=None): '''Enables or disables auto refresh process''' if enabled is None: return self.auto_refresh_enabled else: self.auto_refresh_enabled = enabled def chat_ids(self, new_chat_id=None, command=None): '''Returns the chat ids allowed or adds a new chat id allowed if the password given is correct ''' if new_chat_id is None: return self.chat_id_list else: is_new = True for chat_id in self.chat_id_list: if new_chat_id == chat_id: if command is not None: if '/exit' in command: self.chat_id_list.remove(new_chat_id) self.bot.sendMessage(new_chat_id, 'Goodbye') logger.info('Allowed chat ids:') logger.debug(self.chat_id_list) return False else: is_new = False else: is_new = False if is_new: if self.chat_password in command: self.chat_id_list.append(new_chat_id) logger.info('Allowed chat ids:') logger.debug(self.chat_id_list) self.set_reply_text('Welcome') self.refresh_dashboard() else: self.bot.sendMessage(new_chat_id, 'Type correct password') return False else: return True def store_dash_msg(self, chat_count, msg=None): '''Returns and stores the last message id to delete the message afterwards ''' if len(self.last_dash_msg) > chat_count: if msg is None: return self.last_dash_msg[chat_count] else: self.last_dash_msg[chat_count] = msg elif len(self.last_dash_msg) == chat_count: if msg is None: return False else: self.last_dash_msg.append(msg) else: logger.error("Store last msg: {}".format( sys._getframe().f_code.co_name)) return False def store_reply_msg(self, chat_count, msg=None): '''Returns and stores the last message id to delete the message afterwards ''' if len(self.last_reply_msg) > chat_count: if msg is None: return self.last_reply_msg[chat_count] else: self.last_reply_msg[chat_count] = msg elif len(self.last_reply_msg) == chat_count: if msg is None: return False else: self.last_reply_msg.append(msg) else: logger.error("Store last msg: {}".format( sys._getframe().f_code.co_name)) return False def delete_last_msg(self, bot, chat_count): '''Deletes last message sent''' try: bot.deleteMessage(self.last_dash_msg[chat_count]) bot.deleteMessage(self.last_reply_msg[chat_count]) except Exception: logger.warning('First msg?') def send_msg(self, message): for chat_id in self.chat_id_list: self.bot.sendMessage(chat_id, message) def send_chat_action(self, action): '''Valid actions: upload_audio, typing ''' for chat_id in self.chat_id_list: self.bot.sendChatAction(chat_id, action)
def __init__(self, state): self.state = state self.config = ConfigControls() self.MAX_SEARCH_RESULTS = 20 self.spotipy_VERSION = spotipy.VERSION
class MopidyControls: def __init__(self, state): self.state = state self.config = ConfigControls() self.MAX_SEARCH_RESULTS = 20 self.spotipy_VERSION = spotipy.VERSION def refresh_token(self): ''' Get credentials from config.txt ''' text_spotipy = self.config.get_section('Spotipy') for line in text_spotipy: if 'username' in line: my_username = line.split(' = ')[1].strip() elif 'client_id' in line: my_client_id = line.split(' = ')[1].strip() elif 'client_secret' in line: my_client_secret = line.split(' = ')[1].strip() elif 'redirect_uri' in line: my_redirect_uri = line.split(' = ')[1].strip() self.token = util.prompt_for_user_token( my_username, 'user-read-private ' + 'user-read-currently-playing ' + 'user-modify-playback-state ' + 'user-read-playback-state ' + 'user-read-recently-played', client_id=my_client_id, client_secret=my_client_secret, redirect_uri=my_redirect_uri) self.spotify = spotipy.Spotify(auth=self.token) logger.info('Spotify token refreshed') return self.token def play(self, playlist=None): '''play the currently active track''' if self.state.music_status.mopidy_playing: try: if playlist is None: output = subprocess.check_output(['mpc', 'play']) else: output = subprocess.check_output(['mpc', 'play', playlist]) return False except: logger.error("Mopidy control error: {}".format( sys._getframe().f_code.co_name)) return False else: try: if not self.state.music_status.playing: self.spotify.start_playback() return True except: logger.error("Mopidy control error: {}".format( sys._getframe().f_code.co_name)) return False def pause(self): '''Pause playback''' if self.state.music_status.mopidy_playing: try: output = subprocess.check_output(['mpc', 'pause']) return False except: logger.error("Mopidy control error: {}".format( sys._getframe().f_code.co_name)) return False else: try: if self.state.music_status.playing: self.spotify.pause_playback() return True except: logger.error("Mopidy control error: {}".format( sys._getframe().f_code.co_name)) return False def toggle(self): '''Toggles play and pause''' if self.state.music_status.mopidy_playing: try: output = subprocess.check_output(['mpc', 'toggle']) return False except: logger.error("Mopidy control error: {}".format( sys._getframe().f_code.co_name)) return False else: try: if self.state.music_status.playing: self.spotify.pause_playback() else: self.spotify.start_playback() return True except: logger.error("Mopidy control error: {}".format( sys._getframe().f_code.co_name)) return False def list_playlist(self): '''Outputs a list of playlists''' try: output = subprocess.check_output(['mpc', 'lsplaylist']) return output.decode('utf-8') except: logger.error("Mopidy control error: {}".format( sys._getframe().f_code.co_name)) return False def search(self, query, type_): '''Outputs a list of the search''' try: output = subprocess.check_output(['mpc', 'search', type_, query]) return output.decode('utf-8') except: logger.error("Mopidy control error: {}".format( sys._getframe().f_code.co_name)) return False def mopidy_status(self): '''Outputs the state''' try: output = subprocess.check_output(['mpc']) return output.decode('utf-8') except: logger.error("Mopidy control error: {}".format( sys._getframe().f_code.co_name)) return False def load(self, name, name_type): LOADING_STATUS_REFRESH_COUNTS = 3 try: if 'playlist' in name_type or 'pl' in name_type: output = subprocess.check_output(['mpc', 'load', name]) elif 'uri' in name_type: logger.info('load: ' + name) auto_refresh_enabled = self.state.auto_refresh_enabled if 'spotify:user:spotify' in name: #Solves the error adding playlists created by spotify while self.state.auto_refresh_enabled: self.state.auto_refresh_enabled = False [tracks_list, uri_list] = self.tracks_spotify(name) if not tracks_list and not uri_list: logger.error("Mopidy control error: {}".format( sys._getframe().f_code.co_name)) return False uri_counts = LOADING_STATUS_REFRESH_COUNTS for uri in uri_list: if uri_counts % LOADING_STATUS_REFRESH_COUNTS == 0: self.state.send_chat_action('upload_audio') uri_counts = 0 uri_counts += 1 output = subprocess.check_output(['mpc', 'add', uri]) self.state.auto_refresh_enabled = auto_refresh_enabled else: try: output = subprocess.check_output(['mpc', 'add', name]) except: if not 'youtube.com' in name: logger.warning('Alternative loading') while self.state.auto_refresh_enabled: self.state.auto_refresh_enabled = False [tracks_list, uri_list] = self.tracks_spotify(name) if not tracks_list and not uri_list: logger.error("Mopidy control error: {}".format( sys._getframe().f_code.co_name)) return False uri_counts = LOADING_STATUS_REFRESH_COUNTS for uri in uri_list: if uri_counts % LOADING_STATUS_REFRESH_COUNTS == 0: self.state.send_chat_action('upload_audio') uri_counts = 0 uri_counts += 1 output = subprocess.check_output( ['mpc', 'add', uri]) self.state.auto_refresh_enabled = auto_refresh_enabled else: logger.error("Mopidy control error: {}".format( sys._getframe().f_code.co_name)) return False else: logger.error("Mopidy control error: Bad input {}".format( sys._getframe().f_code.co_name)) output = '' return False logger.info('Load success') return True except Exception as error: logger.error(('Mopidy control error {} \n' + error).format( sys._getframe().f_code.co_name)) while not self.state.auto_refresh_enabled: self.state.auto_refresh_enabled = True return False def load_local_song(self): '''Used when no internet connection, loads a local file song''' output = subprocess.check_output( ['mpc', 'add', 'local:track:WakePi_local_song.mp3']) return True def clear(self): '''Clears the current play''' if self.state.music_status.mopidy_playing: try: output = subprocess.check_output(['mpc', 'clear']) return False except: logger.error("Mopidy control error: {}".format( sys._getframe().f_code.co_name)) return False else: try: if self.state.music_status.playing: self.spotify.pause_playback() return True except: logger.error("Mopidy control error: {}".format( sys._getframe().f_code.co_name)) return False def random(self, mode=None): '''Toggles random mode''' if self.state.music_status.mopidy_playing: try: if mode is None: output = subprocess.check_output(['mpc', 'random']) elif (mode is 'on') or (mode is 'off'): output = subprocess.check_output(['mpc', 'random', mode]) return False except: logger.error("Mopidy control error: {}".format( sys._getframe().f_code.co_name)) return False else: try: if self.state.music_status.random or mode is 'off': self.spotify.shuffle(False) else: self.spotify.shuffle(True) return True except: logger.error("Mopidy control error: {}".format( sys._getframe().f_code.co_name)) return False def repeat(self, mode=None): '''Toggles repeat mode''' if self.state.music_status.mopidy_playing: try: if mode is None: output = subprocess.check_output(['mpc', 'repeat']) elif (mode is 'on') or (mode is 'off'): output = subprocess.check_output(['mpc', 'repeat', mode]) return False except: logger.error("Mopidy control error: {}".format( sys._getframe().f_code.co_name)) return False else: try: if self.state.music_status.repeat or mode is 'off': self.spotify.repeat('off') else: self.spotify.repeat('context') return False except: logger.error("Mopidy control error: {}".format( sys._getframe().f_code.co_name)) return False def next(self): '''Next song in list''' if self.state.music_status.mopidy_playing: try: output = subprocess.check_output(['mpc', 'next']) return False except: logger.error("Mopidy control error: {}".format( sys._getframe().f_code.co_name)) return False else: try: self.spotify.next_track() return True except: logger.error("Mopidy control error: {}".format( sys._getframe().f_code.co_name)) return False def prev(self): '''Previous song''' if self.state.music_status.mopidy_playing: status = self.get_status() song_percentage = status.song_percentage try: song_percentage = max(min(song_percentage, 100), 0) if song_percentage < 10: output = subprocess.check_output(['mpc', 'prev']) else: output = subprocess.check_output(['mpc', 'cdprev']) return False except: logger.error("Mopidy control error: {}".format( sys._getframe().f_code.co_name)) return False else: try: self.spotify.previous_track() return True except: logger.error("Mopidy control error: {}".format( sys._getframe().f_code.co_name)) return False def seek(self, increment): '''Seeks a percentage of the song''' status = self.get_status() song_percentage = status.song_percentage + increment if self.state.music_status.mopidy_playing: try: song_percentage = max(min(song_percentage, 100), 0) song_percentage = str(song_percentage) + '%' output = subprocess.check_output( ['mpc', 'seek', song_percentage]) return False except: logger.error("Mopidy control error: {}".format( sys._getframe().f_code.co_name)) return False else: try: position_ms = status.duration_s * 10 * song_percentage self.spotify.seek_track(position_ms) return True except: logger.error("Mopidy control error: {}".format( sys._getframe().f_code.co_name)) return False def volume_mopidy(self, volume, increase=None): '''Changes the volume''' try: volume = max(min(volume, 100), 0) if increase is None: volume_change = str(volume) else: if increase: volume_change = '+' + str(volume) else: volume_change = '-' + str(volume) output = subprocess.check_output(['mpc', 'volume', volume_change]) return output.decode('utf-8') except: logger.error("Mopidy control error: {}".format( sys._getframe().f_code.co_name)) return False def volume_sys(self, volume_change): try: volume_perc = self.get_volume_sys() new_volume_perc = max(min(volume_perc + volume_change, 100), 0) if self.set_volume_sys(new_volume_perc): return True else: return False except: return False def set_volume_sys(self, volume_perc): try: db = int((106 * (volume_perc / 2 + 50))) - 10200 subprocess.check_output( ['sudo', 'amixer', 'set', 'PCM', '--', str(db)]) return True except: return False def get_volume_sys(self): output = subprocess.check_output(['sudo', 'amixer', 'get', 'PCM']) db = int( output.decode("utf-8").split('\n')[-2].split('[')[0].split( 'Playback')[1].strip(' ')) volume_perc = int(((db + 10200) / 106 - 50) * 2) return volume_perc def shuffle(self): '''Shuffles the current playlist''' try: output = subprocess.check_output(['mpc', 'shuffle']) return True except: logger.error("Mopidy control error: {}".format( sys._getframe().f_code.co_name)) return False #This function only works with spotify def search_spotify(self, query, type_): '''Improved search function that returns a name and a URI list''' try: uri_list = [] search_list = [] try: info = self.spotify.search(q=query, limit=self.MAX_SEARCH_RESULTS, type=type_) except: self.refresh_token() info = self.spotify.search(q=query, limit=self.MAX_SEARCH_RESULTS, type=type_) if 'playlist' in type_: for playlist in info['playlists']['items']: uri_list.append(playlist['uri']) info_text = u'\U0001F3A7 \U0001F4DD' + playlist['name'] search_list.append(info_text) elif 'track' in type_: for track in info['tracks']['items']: uri_list.append(track['uri']) info_text = (u'\U0000270F \U0001F3B5' + track['name'] + ' ' + u'\U0001F919 \U0001F399' + track['artists'][0]['name']) search_list.append(info_text) elif 'artist' in type_: for artist in info['artists']['items']: uri_list.append(artist['uri']) info_text = u'\U0001F919 \U0001F399' + artist['name'] search_list.append(info_text) elif 'album' in type_: for album in info['albums']['items']: uri_list.append(album['uri']) info_text = (u'\U0001F3B6 \U0001F4BF' + album['name'] + ' ' + u'\U0001F919 \U0001F399' + album['artists'][0]['name']) search_list.append(info_text) return [search_list, uri_list] except: logger.error("Mopidy control error: {}".format( sys._getframe().f_code.co_name)) return [False, False] #This function only works with spotify def list_playlist_spotify(self): '''Outputs the current spotify user list of playlists''' try: uri_list = [] playlist_list = [] try: info = self.spotify.current_user_playlists() except: self.refresh_token() info = self.spotify.current_user_playlists() for playlist in info['items']: uri_list.append(playlist['uri']) info_text = u'\U0001F3A7 \U0001F4DD' + playlist['name'] playlist_list.append(info_text) return [playlist_list, uri_list] except: logger.error("Mopidy control error: {}".format( sys._getframe().f_code.co_name)) return [False, False] #This function only works with spotify def tracks_spotify(self, uri): '''Outputs the track list of an album, playlist, or artist uri''' try: tracks_list = [] uri_list = [] if 'playlist' in uri: username = uri.split(':')[2] playlist_id = uri.split(':')[4] try: info = self.spotify.user_playlist(username, playlist_id) except: self.refresh_token() info = self.spotify.user_playlist(username, playlist_id) for track in info['tracks']['items']: info_text = u'\U0000270F \U0001F3B5' + track['track'][ 'name'] tracks_list.append(info_text) uri_list.append(track['track']['uri']) elif 'artist' in uri: try: info = self.spotify.artist_top_tracks(uri) except: self.refresh_token() info = self.spotify.artist_top_tracks(uri) for track in info['tracks']: info_text = u'\U0000270F \U0001F3B5' + track['name'] tracks_list.append(info_text) uri_list.append(track['uri']) elif 'album' in uri: try: info = self.spotify.album(uri) except: self.refresh_token() info = self.spotify.album(uri) for track in info['tracks']['items']: info_text = u'\U0000270F \U0001F3B5' + track['name'] tracks_list.append(info_text) uri_list.append(track['uri']) return [tracks_list, uri_list] except: logger.error("Mopidy control error: {}".format( sys._getframe().f_code.co_name)) return [False, False] def albums_spotify(self, uri): '''Outputs the album list of an artist uri''' try: album_list = [] uri_list = [] if 'artist' in uri: try: info = self.spotify.artist_albums(uri) except: self.refresh_token() info = self.spotify.artist_albums(uri) for album in info['items']: info_text = u'\U0001F3B6 \U0001F4BF' + album['name'] album_list.append(info_text) uri_list.append(album['uri']) else: logger.error('Cannot search albums of a non artist uri') return [album_list, uri_list] except: logger.error("Mopidy control error: {}".format( sys._getframe().f_code.co_name)) return [False, False] def get_status(self): '''Build the status namedtuple''' status_full = self.mopidy_status() status = namedtuple( "status", ' '.join([ "mopidy_playing", "playing", "song", "volume", "repeat", "random", "single", "consume", "artist", "song_number", "song_time", "song_percentage", "progress_s", "duration_s" ])) if not status_full: '''Mopidy not enabled''' status.playing = False status.song = ' *!!* ' status.artist = 'Music server off' status.song_number = '#0/0' status.song_time = '0:00/0:00' status.volume = 'xx' status.repeat = False status.random = False status.single = False status.consume = False status.song_percentage = 0 return status if len(status_full.splitlines()) == 1: #Mopidy has no playback status = self.get_status_spotify(status) else: status.mopidy_playing = True for index, line in enumerate(status_full.splitlines()): if index == 2: for section in line.split(' '): if 'volume:' in section: status.volume = str(self.get_volume_sys()) + '%' elif 'repeat' in section: if 'on' in section: status.repeat = True else: status.repeat = False elif 'random' in section: if 'on' in section: status.random = True else: status.random = False elif 'single' in section: if 'on' in section: status.single = True else: status.single = False elif 'consume' in section: if 'off' in section: status.consume = False else: status.consume = True elif index == 0: section = line.split('-') if len(section) > 1: status.song = section[1].strip(' ') status.artist = section[0].strip(' ') else: status.song = section[0].strip(' ') status.artist = 'unknown' elif index == 1: '''Remove silly blanks''' sections = line.split(' ') deleted = 0 for index, section in enumerate(sections): if sections[index - deleted].strip(' ') == '': del sections[index - deleted] deleted += 1 '''Analyze line''' if '[playing]' in line: status.playing = True else: status.playing = False status.song_number = sections[1] status.song_time = sections[2] status.song_percentage = int(sections[3].strip(' ').strip( '(').strip(')').strip('%')) status.progress_s = ( int(sections[2].split('/')[0].split(':')[0]) * 60 + int(sections[2].split('/')[0].split(':')[1])) status.duration_s = ( int(sections[2].split('/')[1].split(':')[0]) * 60 + int(sections[2].split('/')[1].split(':')[1])) return status def get_status_spotify(self, status): '''Returns the status of the spotify playback, called when the mopidy status is empty''' logger.info('Getting spotify playback') try: status_full = self.spotify.current_playback() except: self.refresh_token() status_full = self.spotify.current_playback() if status_full: status.mopidy_playing = False if status_full['is_playing']: status.playing = True else: status.playing = False status.song = status_full['item']['name'] status.artist = status_full['item']['artists'][0]['name'] status.song_number = '#' + str( status_full['item']['track_number']) + '/?' status.progress_s = int(int(status_full['progress_ms']) / 1000) status.duration_s = int( int(status_full['item']['duration_ms']) / 1000) status.song_time = (str(int(status.progress_s / 60)) + ':' + ('0' + str(int(status.progress_s % 60)))[-2:] + '/' + str(int(status.duration_s / 60)) + ':' + ('0' + str(int(status.duration_s % 60)))[-2:]) status.volume = str(self.get_volume_sys()) + '%' if 'context' in status_full['repeat_state']: status.repeat = True else: status.repeat = False if status_full['shuffle_state']: status.random = True else: status.random = False status.single = False status.consume = False status.song_percentage = int(status.progress_s / status.duration_s * 100) else: status.mopidy_playing = True status.playing = False status.song = 'No song' status.artist = 'No artist' status.song_number = '#0/0' status.song_time = '0:00/0:00' status.volume = str(self.get_volume_sys()) + '%' status.repeat = False status.random = False status.single = False status.consume = False status.song_percentage = 0 status.progress_s = 0 status.duration_s = 0 logger.info('Got spotify playback') return status
def __init__(self, state): self.state = state self.config = ConfigControls()
class CalendarCheck: def __init__(self, state): self.state = state self.config = ConfigControls() def get_cal_urls(self): '''Gets the calendars urls from the config.txt''' urls = self.config.get_section('Calendar') return urls def add_cal_url(self, url): '''Adds a url to the config file''' urls = self.get_cal_urls() urls.append(url) self.config.set_section('Calendar', urls) return True def remove_cal_url(self, url): urls = self.get_cal_urls() try: urls.remove(url) self.config.set_section('Calendar', urls) return True except: return False def get_week_cal(self): '''Extracts the weekly calendar from the ical urls on the config file''' logger.info('Getting week calendar') today = datetime.datetime.utcnow().date() next_week = today + datetime.timedelta(7) week_cal = Calendar() week_cal.add('x-wr-calname', "Weekly Combined Calendar") urls = self.get_cal_urls() for url in urls: logger.debug(url) try: # Error avoider: Tries to read the calendar several times attempts = 0 while attempts < 5: if attempts < 4: try: req = requests.get(url) attempts = 5 except: attempts += 1 else: req = requests.get(url) attempts = 5 # Error avoider finished if req.status_code != 200: logger.error("Error {} fetching {}: {}".format( url, req.status_code, req.text)) continue cal = Calendar.from_ical(req.text) for event in cal.walk("VEVENT"): end = event.get('dtend') if end: WEEK_EVENT = False if hasattr(end.dt, 'date'): date = end.dt.date() else: date = end.dt if date >= today and date < next_week: WEEK_EVENT = True elif 'RRULE' in event: try: rrule = event['RRULE'] until = rrule.get('until')[0] if today < until.date(): # Only weekly repeated events are supported if 'WEEKLY' in rrule.get('freq')[0]: WEEK_EVENT = True except Exception as error: logger.error(("{} rrule\n" + error).format( sys._getframe().f_code.co_name)) if WEEK_EVENT: copied_event = Event() for attr in event: if type(event[attr]) is list: for element in event[attr]: copied_event.add(attr, element) else: copied_event.add(attr, event[attr]) week_cal.add_component(copied_event) except Exception as error: # Add counter to avoid false errors logger.warning('Invalid calendar, removing\n' + url + '\n' + error) self.remove_cal_url(url) for chat_id in self.state.chat_id_list: self.state.bot.sendMessage( chat_id, 'Removed invalid calendar url:\n' + url) logger.info('Got calendars') return week_cal def get_week_events_local_times(self): '''Gets the starting times of the next week events and returns them in a list ordered in days remaining events = [[datetime.datetime,...], [...], ...] ''' week_cal = self.get_week_cal() now = datetime.datetime.utcnow() utcoffset = datetime.datetime.now( datetime.timezone.utc).astimezone().tzinfo.utcoffset(None) today_local = (datetime.datetime.utcnow() + utcoffset).date() events = [[], [], [], [], [], [], []] for event in week_cal.walk("VEVENT"): if 'RRULE' in event: # Only weekly repeated events are supported try: ref_start = event.get('dtstart') if isinstance(ref_start.dt, datetime.datetime ): # Check is not a full day event reference = ref_start.dt.replace(tzinfo=None) rrule = event['RRULE'] if 'BYDAY' in rrule: day_list = rrule.get('byday') elif 'WKST' in rrule: day_list = rrule.get('wkst') for day in day_list: for index, day_string in enumerate( ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU']): if day in day_string: reference_weekday = index break event_add_day = reference_weekday - now.weekday() if event_add_day < 0: event_add_day += 7 event_day = now.day + event_add_day event_start = datetime.datetime( now.year, now.month, event_day, reference.hour, reference.minute) #Check for excluded dates is_excluded = False if 'EXDATE' in event: exclusions = event.get('exdate') if not isinstance(exclusions, list): exclusions = [exclusions] for ex_list in exclusions: for ex_day in ex_list.dts: if ex_day.dt.replace( tzinfo=None ) - event_start == datetime.timedelta( 0): is_excluded = True break if not is_excluded: event_start_local = event_start # + utcoffset '''I don't know why it is already local''' days_to_go = event_start_local.weekday( ) - today_local.weekday() if days_to_go < 0: days_to_go += 7 events[days_to_go].append(event_start_local) except Exception as error: logger.error(" {}: rrule\n{}".format( sys._getframe().f_code.co_name, error)) else: start = event.get('dtstart') if start: try: if isinstance(start.dt, datetime.datetime ): # Check is not a full day event event_start = start.dt.replace(tzinfo=None) event_start_local = event_start + utcoffset days_to_go = event_start_local.weekday( ) - today_local.weekday() if days_to_go < 0: days_to_go += 7 events[days_to_go].append(event_start_local) except Exception as error: logger.error(" {}: not rrule\n{}".format( sys._getframe().f_code.co_name, error)) sorted_events = [[], [], [], [], [], [], []] for weekday, day in enumerate(events): sorted_events[weekday] = sorted(day) return sorted_events def get_day_first_events_localtime(self, week_events): '''Gets a list with the first events of each weekday first_events = [datetime.time,...] ''' first_events = [None, None, None, None, None, None, None] for days_to_go, event in enumerate(week_events): if len(event) > 0: first_events[days_to_go] = event[0] return first_events
now = datetime.datetime.utcnow() file_log = now.strftime('%d_%m_%y %H_%M') + '.log' logger = logging.getLogger('WakePi') logger.setLevel(logging.DEBUG) fh = logging.FileHandler(file_log) fh.setLevel(logging.INFO) ch = logging.StreamHandler() ch.setLevel(logging.DEBUG) formatter = logging.Formatter('[%(levelname)7s] %(asctime)s: %(message)s') fh.setFormatter(formatter) ch.setFormatter(formatter) logger.addHandler(fh) logger.addHandler(ch) logger.info('Start initialization') '''Initialize modules''' config = ConfigControls() bot = telepot.Bot(get_bot_token()) state = State(bot) cp = CommandProcess(state, bot) mopidy = MopidyControls(state) logger.info('Spotipy version ' + mopidy.spotipy_VERSION) cal_check = CalendarCheck(state) alarm = AlarmControls(state) MessageLoop(bot, { 'chat': on_chat_message, 'callback_query': on_callback_query }).run_as_thread() state.week_events = state.cal_check.get_week_events_local_times() alarm.set_auto_alarms(state.week_events) '''Parallel process''' if __name__ == "__main__":