def get_weather(self, location, days=0): api_key = self.config.get('api_key', None) assert api_key, 'api_key configuration is missing' if days > 0: api_url = self.api_url_forecast.format(api_key=api_key, location=location, days=days + 1) else: api_url = self.api_url_current.format(api_key=api_key, location=location) print(api_url) response = requests.get(api_url, headers=self.headers) if response.status_code != requests.codes.ok: raise CommandError(response) res = response.json() code = int(res['cod']) if code != requests.codes.ok: raise CommandError(res.get('message', res)) return res
def _at_add(self, msg, schedule, cmd_name, *cmd_args, **job_kwargs): """Schedule command execution at specific time and date""" # Validate schedule schedule = str(schedule) if schedule.startswith('+'): try: dt = datetime.now() + timedelta(minutes=int(schedule)) except ValueError: raise CommandError('Invalid date-time (required format: +<integer>)') else: try: dt = datetime.strptime(schedule, '%Y-%m-%d-%H-%M') except ValueError: raise CommandError('Invalid date-time (required format: YYYY-mm-dd-HH-MM)') # Validate command cmd = self.xmpp.commands.get_command(cmd_name) if not cmd: raise CommandError('Invalid command') # Check user permission user = self.xmpp.get_jid(msg) if not cmd.is_jid_permitted_to_run(self.xmpp, user): raise CommandError('Permission denied') # Create message (the only argument needed for command) with body representing the whole command body = ' '.join([cmd.name] + ["%s" % i for i in cmd_args]) msg = self.xmpp.msg_copy(msg, body=body) job = self.xmpp.cron.crontab.add_at(cmd.get_fun(self.xmpp), dt, msg, user, **job_kwargs) logger.info('Registered one-time cron job: %s', job.display()) return 'Scheduled job ID **%s** scheduled at %s' % (job.name, job.schedule)
def _set_status(self, show, status=None): """Send presence status""" if show not in self._status_show_types: raise CommandError('Invalid status type') if show == 'online': show = None try: self.xmpp.client.send_presence(pstatus=status, pshow=show) except IqError as e: raise CommandError('Status update failed: __%s__' % getattr(e, 'condition', str(e)))
def remind(self, msg, *args): """ List, add, or delete reminders. List all scheduled reminders. Usage: remind Schedule reminder at specific time and date. Usage: remind add +minutes <message> Usage: remind add Y-m-d-H-M <message> Remove reminder from queue of scheduled reminders. Usage: remind del <reminder ID> """ args_count = len(args) if args_count > 0: action = args[0] if action == 'add': if args_count < 2: raise MissingParameter else: at_add_params = (args[1], self._reminder_command, self.xmpp.get_jid(msg), self._reminder) + args[2:] return self._at_add(msg, *at_add_params, at_reply_output=False) elif action == 'del': if args_count < 2: raise MissingParameter else: return self._at_del(msg, args[1]) else: raise CommandError('Invalid action') return self._at_list(msg, reminder=True)
def _get_output_stream(self, name): """Stream Ludolph command output""" for line in self.output: yield line if self.returncode != 0: raise CommandError('Command "%s" exited with non-zero status %s' % (name, self.returncode))
def _get_output(self, name): """Classic Ludolph command output""" out = '\n'.join(self.output) if self.returncode == 0: return out else: raise CommandError(out)
def _execute(self, msg, name, cmd, *args): """Execute a command and return stdout or raise CommandError""" try: if cmd is None: # pass-through mode cmd = shlex.split(msg['body']) else: cmd = shlex.split(cmd) cmd.extend(list(map(str, args))) except Exception: raise CommandError('Could not parse command parameters') logger.info('Running dynamic command: %s', cmd) try: return Process(cmd).cmd_output(name, stream=msg.stream_output) except CommandError: raise except Exception as e: raise CommandError('Could not run command (%s)' % e)
def kick(self, msg, user): """ Kick user from multi-user chat room (room admin only). Usage: kick <JID> """ nick = self._get_nick(user) if not nick: raise CommandError('User **%s** is not in MUC room' % user) try: self.xmpp.muc.setRole(self.xmpp.room, nick, 'none') except (IqError, ValueError) as e: err = getattr(e, 'condition', str(e)) raise CommandError( 'User **%s** could not be kicked from MUC room: __%s__' % (user, err)) return 'User **%s** kicked from MUC room' % user
def _at_del(self, msg, name): """Remove scheduled job""" try: job_id = int(name) except (ValueError, TypeError): raise CommandError('Invalid job ID') crontab = self.xmpp.cron.crontab job = crontab.get(job_id, None) if job and job.onetime: user = self.xmpp.get_jid(msg) if job.owner == user or self.xmpp.is_jid_admin(user): crontab.delete(job_id) logger.info('Deleted one-time cron jobs: %s', job.display()) return 'Scheduled job ID **%s** deleted' % job_id else: raise CommandError('Permission denied') raise CommandError('Non-existent job ID')
def _set_room_subject(self, text, mfrom=None): """Set room subject""" msg = self.xmpp.client.Message() msg['to'] = self.xmpp.room msg['from'] = mfrom msg['type'] = 'groupchat' # noinspection PyProtectedMember msg._set_sub_text('subject', text or '', keep=True) try: msg.send() except IqError as e: raise CommandError('Room topic update failed: __%s__' % getattr(e, 'condition', str(e)))
def _message_send(self, jid, msg): """Send new xmpp message. Used by message command and /message webhook""" if jid == self.xmpp.room: mtype = 'groupchat' elif jid in self.xmpp.client_roster: mtype = 'normal' else: raise CommandError('User "%s" not in roster' % jid) logger.info('Sending message to "%s"', jid) logger.debug('\twith body: "%s"', msg) self.xmpp.msg_send(jid, msg, mtype=mtype) return 'Message sent to **%s**' % jid
def motd(self, msg, action=None, text=None): """ Show, set or remove message of the day. Show message of the day (room user only). Usage: motd Set message of the day (room admin only). Usage: motd set <text> Delete message of the day and disable automatic announcements (room admin only). Usage: motd del """ if action: if not self.xmpp.is_jid_room_admin(self.xmpp.get_jid(msg)): raise PermissionDenied if action == 'del': self.room_motd = None return 'MOTD successfully deleted' elif action == 'set': if not text: raise CommandError('Missing text') # Get original version from message body (strip command and sub-command) self.room_motd = msg['body'].lstrip().split(None, 2)[-1] # Announce new motd into room self.xmpp.msg_send(self.xmpp.room, self.room_motd, mtype='groupchat') return 'MOTD successfully updated' else: raise CommandError('Invalid action') if self.room_motd is None: return '(MOTD disabled)' else: return self.room_motd
def at(self, msg, *args): """ List, add, or delete jobs for later execution. List all scheduled jobs. Usage: at Schedule command execution at specific time and date. Usage: at add +minutes <command> [command parameters...] Usage: at add Y-m-d-H-M <command> [command parameters...] Remove command from queue of scheduled jobs. Usage: at del <job ID> """ if not self.xmpp.cron: raise CommandError( 'Cron support is disabled in Ludolph configuration file') args_count = len(args) if args_count > 0: action = args[0] if action == 'add': if args_count < 3: raise MissingParameter else: return self._at_add(msg, *args[1:]) elif action == 'del': if args_count < 2: raise MissingParameter else: return self._at_del(msg, args[1]) else: raise CommandError('Invalid action') return self._at_list(msg)
def version(self, msg, plugin=None): """ Display version of Ludolph or registered plugin. Usage: version [plugin] """ if plugin: mod, obj = self.xmpp.plugins.get_plugin(plugin) if mod: return '**%s** version: %s' % (mod, obj.get_version()) else: raise CommandError('**%s** isn\'t a Ludolph plugin. Check help for available plugins.' % plugin) return '**Ludolph** version: %s' % self.get_version()
def invite(self, msg, user=None): """ Invite user or yourself to multi-user chat room. Usage: invite [JID] """ if not user: user = self.xmpp.get_jid(msg) if not self.xmpp.is_jid_room_user(user): raise CommandError( 'User **%s** is not allowed to access the MUC room' % user) self.xmpp.muc.invite(self.xmpp.room, user) return 'Inviting **%s** to MUC room %s' % (user, self.xmpp.room)
def _avatar_set(self, msg, avatar_name): """Set avatar for Ludolph (admin only)""" if os.path.splitext(avatar_name)[-1] not in self._avatar_allowed_extensions: raise CommandError('You have requested a file that is not supported') avatar = None available_avatar_directories = self._get_avatar_dirs() for avatar_dir in available_avatar_directories: # Create full path to file requested by user avatar_file = os.path.join(avatar_dir, avatar_name) # Split absolute path for check if user is not trying to jump outside allowed dirs path, name = os.path.split(os.path.abspath(avatar_file)) if path not in available_avatar_directories: raise CommandError('You are not allowed to set avatar outside defined directories') try: with open(avatar_file, 'rb') as f: avatar = f.read() except (OSError, IOError): avatar = None else: break if not avatar: raise CommandError('Avatar "%s" has not been found.\n' 'You can list available avatars with the command: **avatar-list**' % avatar_name) self.xmpp.msg_reply(msg, 'I have found the selected avatar, changing it might take few seconds...', preserve_msg=True) xep_0084 = self.xmpp.client.plugin['xep_0084'] avatar_type = 'image/%s' % imghdr.what('', avatar) avatar_id = xep_0084.generate_id(avatar) avatar_bytes = len(avatar) try: logger.debug('Publishing XEP-0084 avatar data') xep_0084.publish_avatar(avatar) except XMPPError as e: logger.error('Could not publish XEP-0084 avatar: %s' % e.text) raise CommandError('Could not publish selected avatar') try: logger.debug('Publishing XEP-0153 avatar vCard data') self.xmpp.client.plugin['xep_0153'].set_avatar(avatar=avatar, mtype=avatar_type) except XMPPError as e: logger.error('Could not publish XEP-0153 vCard avatar: %s' % e.text) raise CommandError('Could not set vCard avatar') self.xmpp.msg_reply(msg, 'Almost done, please be patient', preserve_msg=True) try: logger.debug('Advertise XEP-0084 avatar metadata') xep_0084.publish_avatar_metadata([{ 'id': avatar_id, 'type': avatar_type, 'bytes': avatar_bytes }]) except XMPPError as e: logger.error('Could not publish XEP-0084 metadata: %s' % e.text) raise CommandError('Could not publish avatar metadata') return 'Avatar has been changed :)'