Code Monkey home page Code Monkey logo

slacker's Introduction

Build Status

slacker

Slacker is a tool designed to make it easier to do admin tasks and general utility for several workspaces (teams). It supports a REPL for inputting and evaluating commands, along with a CLI for running a single command.

demo

Table of Contents

First Run

The first invocation of Slacker requires to setup a workspace using the --init argument:

% ./slacker.py --init
Input workspace token: ****************************************************************************
Added new workspace 'myworkspace' to config and made it active.
You can now run slacker normally.
myworkspace>

Slacker is ready for use afterwards with "myworkspace" as the active workspace. Note that it is recommended to employ a user token, not a bot token!

Tokens can be created here. Never share these tokens!

Commands

Displaying available commands:
  api.test            Checks that the Slack API is online.
  auth.test           Checks authentication and describes user identity.
  channels.list       Displays info about channels on Slack.
  chat.memessage      Post a me message to a channel on Slack.
  chat.postephemeral  Post ephemeral message to a channel on Slack that is only visible to assigned user.
  chat.postmessage    Post message to a channel on Slack.
  config              Shows current config state.
  emoji.list          Lists custom emojis in workspace
  exit                Exits Slacker.
  files.delete        Delete files uploaded to and stored on Slack.
  files.list          Displays info about files on Slack.
  help                Displays general help.
  log                 Displays current log level.
  users.list          Displays info about users on Slack.
  workspace           Displays predefined workspaces and which one is active.

CLI Usage

usage: slacker.py [options] [-- command [args..]]

Useful Slack utilities and REPL.

optional arguments:
  -h, --help     show this help message and exit
  -V, --version  show program's version number and exit
  -v, --verbose  Sets the log level to DEBUG for this session.
  -q, --quiet    Disable stdout logging
  --init         Interactively initialize config and add workspace and API
                 token.
  --check        Checks that all commands are valid.
  --no-tests     Do not do API and auth tests at startup.

Most commands support -h|--help to see how they work. By passing '--', it
signals that Slacker arguments end and a single command and arguments begin.
Slacker will exit after running that command.

Development Environment

Slacker is written in Python 3 and the required modules to be installed can be found in requirements.txt.

To setup a virtual development environment that doesn't pollute the general system it is expected that virtualenv for Python 3+ is installed.

Run make setup to install required modules to ".venv" in the root of the repository, and issue source .venv/bin/activate to activate the environment. From here, the python, python3, pip etc. will map to ".venv" and the modules installed into it.

Docker

A Dockerfile is provided to quickly get up an running with Slacker. To build the Docker image it is recommended to use the docker target in the Makefile make docker, which creates image slacker:local. Start the slacker container by executing docker_run.sh. By default the script will drop you into the REPL, but slacker commands can be given directly to the script.

% ./docker_run.sh -- files.list -c 10

Note: slacker writes a configuration file to disk (~/.slacker). In order to keep the configuration file between subsequent runs the local volume mount in the container is required.

Contributing

See CONTRIBUTING.md.

slacker's People

Contributors

0verbyte avatar netromdk avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

Forkers

moealmaw

slacker's Issues

Documentation

We should have a way of generating documentation for slacker, like make doc. And then put it on GitHub Pages regularly(?), and link to it in the README.

Clean up Slacker.py

Currently Slacker.py contains a few utility functions that should be refactored into respective classes. We should only have main in Slacker.py.

Create workspaces via command

I was thinking that now with the masked tokens input, shouldn't we make it possible to create workspaces via the config command, @voidpirate?

It could be via a --create argument that asks for info just like --init does it, and also checks auth of the token.

Implement Plugin abstract class

The plugins module should define a base Plugin object with an abstract method for execute. Execute should provide the logic to be executed when being called from the registrar handlers.

Configuration file

Rename "slacker/environment/common.py" to "slacker/environment/config.py", and keep log level and REPL prefix in the config with defaults.

Move slacker.environment.common.VERSION into "slacker/environment/constants.py".

Implement the Config class with save/load functionality and a singleton pattern so we can access it from commands without needing to reload from disk each time.

It should default to putting the file at "~/.slacker" and use JSON as format.

Extend files.list to download filtered files

A download URL can be retrieved by using the files.info for a file ID via files.list.

Add -d|--download <folder> to download filtered files to. Similar to how emoji.list --path does it. Put that code in a Downloader class of some kind and reuse it here.

Support command arguments

Each command should have it's own command parser, like argparse.

The first input sequence until whitespace is the command. If it matches a command in the registrar, and there is further input text, then use that command's argument parser. If no argument parser is defined it means that it doesn't accept arguments.

Extend Command with the following per default:

def parse_args(self, args):
  return None

If defining arguments for a command:

def parse_args(self, args):
  parser = argparse.ArgumentParser()
  # .. define arguments ..
  return parser.parse_args(args)

Example of usage:

args = cmd.parse_args(the_input.split())
cmd.action(args)

Make readme more informative

It should first describe the project in short and what it can help people to do. Then we should list how to use it as REPL and single command execution. We should also keep an up-to-date list of commands (at least the useful ones pertaining to Slack API methods). In the end, we should describe how people can contribute to the project and what requirements there are. But this should go into "CONTRIBUTING.md" instead.

Tab-complete commands

Tab-completion with no input should show something like:

> <tab>
X commands/aliases available.. Press tab again to list them all.

The commands should be shown similar to how the help command does it.

Tab-completion with some input should complete to the longest common string of all available commands/aliases, like: > he<tab> becomes > help or > set<tab> becomes > setVersion if, for instance, there are two commands "setVersionMajor" and "setVersionMinor". If tab-completing again, with several candidates available, show same message as the first message above and the second tab-completion expands to all of them.

Only tab complete if character right before tab isn't whitespace and neither if any whitespaces exist between input values, like > help <tab> and > help with this<tab>.

Maybe get some inspiration from how lldb does it.

Stdout log handler format

The output in stdout is too verbose, like:

0xCoders> files
2018-02-04 11:56:35,101 - FilesListCommand - INFO - Listing files..
2018-02-04 11:56:35,422 - FilesListCommand - INFO -   Screen Shot 2018-02-02 at 20.11.30.png              44.36 KiB
2018-02-04 11:56:35,422 - FilesListCommand - INFO -   pkill.png                                           29.43 KiB

Which should be something like:

0xCoders> files
Listing files..
   Screen Shot 2018-02-02 at 20.11.30.png              44.36 KiB
   pkill.png                                           29.43 KiB

Project logging class

This feature would be helpful to debug what's going on using log levels and severity.

Command discovery

Find a way to detect all commands in slacker.commands that derive from slacker.commands.command.Command.

Log command

Command name: log
Alias: l

With no arguments it should print log file in use and the current debug level.

--level|-l LEVEL should set new debug level. Display choices of level.

Save and load log level from config file.

Add slacker argument -v|--verbose that will set log level to debug but not save it to config file.

CLI invocation with single command

We need to support running slacker like this:

$ ./slacker.py some.command --arg1 hello world

It should only run that single command and then exit.

Emoticon download

API endpoint: https://api.slack.com/methods/emoji.list

Command: slacker downloademoticons <local_save_path>

The command should download all custom emoji from a workspace. The local_save_path is a simple JSON format that describes where the emoji are saved on disk and if any emoji aliases were saved.

The JSON format for the save file will be something like this:

{
  "emojis": {
    "metal": "/local_save_path/46ec6f2bb0.png",
    "shipit": "alias:metal"
  }
}

Allow only ASCII commands and aliases

Should we only allow ASCII commands and aliases? At least we should not allow

,;:|&%#"'/\()[]{}=?+-_´`!$§<>

right?
It would be easier to say we only allow [\w\d\.] (case-insensitive regex).

Rename workspace to team

In the Slack API they mostly refer to what we call "workspaces" as "teams". Maybe we should rename it to teams to be 1-to-1?

Workspace configuration

Depends on #7 and #32.

Workspace configurations must be present in the config file, like workspace names and tokens.

Extend Config to expose predefined workspaces {name: token..}. But don't allow creating workspaces inside Slacker just yet to keep things secure. Nobody wants the tokens in a shell history!

Add --init argument to slacker that will create a default config file on disk and ask, on stdin, about workspace name and token, which will be saved to the config file. If a config file is already present it must ask whether the user wants to overwrite the file or stop.

If slacker is run without a config it should say that it doesn't have a config and slacker needs to be run with the --init argument.

Implement WorkspaceCommand with name "workspace" and alias "ws". It should have argument --set <workspace name>. If no arguments are given it should list the names of each known one but not the tokens! Like:

Predefined workspaces:
  0xCoders (active)
  luxionteam

Change the REPL to display the workplace so it's always evident, like workspace>

Examples:

% ./slacker.py
0xCoders> ws
Predefined workspaces:
  0xCoders (active)
  luxionteam
0xCoders> ws --set luxionteam
luxionteam> ws
Predefined workspaces:
  0xCoders
  luxionteam (active)

Let Config keep the active workspace that is also saved to the config file.

Create destination folder for emoji.list when downloading

Expected behavior

Download emojis to a folder should try to create the folder if it doesn't exist.

Actual behavior

Getting the exception:

<class 'FileNotFoundError'> [Errno 2] No such file or directory: '/tmp/testfolder/emoji.png'

Steps to reproduce behavior

% rm -fr /tmp/testfolder
% ./slacker.py -- emoji.list -p /tmp/testfolder

ConfigCommand

The command should list the current config state but not show any tokens. It must not be possible to change the active workspace, or any workspace name either. Same with log level. Basically, everything we have another command for shouldn't be changeable via config command.

Without any arguments it should just dump a dict of config state without the above-mentioned values. And show the file it is currently saved to/loaded from.

Convert quotes for conformity

We decided to use "hello 'sub string'" wrt. quotes. Especially because 'don\'t' looks terrible.

Update CONTRIBUTION.md with info about this fact.

Read-only mode

It might be an idea to introduce an optional, nondestructive read-only mode, which when in effect will disallow all API methods that are destructive or that modifies state.

The idea is that you have a token for a workspace but you want to be sure that you don't do damage by mistake. Like when running files.delete without being careful.

This should be configurable directly with the config command with --set-read-only <True|False>. Every command must then check for config.read_only() and/or we put it into SlackAPI so that for such methods it will simply not send the request, and instead write to log that it didn't do it due to read-only mode.

When in read-only mode, the prompt should state this somehow, like

(read-only) workspace> 

Configurable prompt

The default WORKSPACE> prompt might not be to the liking of everybody, so we should define some custom language for specifying the prompt. It could be simply "%w> " with %w for the active workspace. And perhaps %ro for displaying read-only mode if in effect.

Per default it should use "%ro%w> " to be expanded into "workspace> " in normal mode and "(read-only) workspace> " in read-only mode.

Or we use {w} for workspace and {ro:read only string} so that the text can be user-defined including spaces etc. In this case the default would be "{ro:(read-only) }{w}> " instead.

Rename the config.repl_prefix to config.prompt. Later on we might add support for a right-prompt with config.right_prompt.

Additional entities could be added:

  • {u} for username, "username"
  • {uid} for user ID, "U8C3V3OQ"
  • {wid} for workspace ID, like "T9F3H7VLP"
  • {url} for workspace URL, like "https://workspace.slack.com/"

These are retrieved using auth.test and should be saved at startup for current auth info (saved in AuthInfo singleton or added to Config, perhaps even extend the saved workspaces with all this info simply so it isn't necessary to query all the time?) and then updated if another workspace is made active with workspace. The keep-it-in-the-config approach could look like the following:

{
  "workspaces": {
    "0xCoders": {
       "token": "TOKEN",
       "wid": "WORKSPACE_ID",
       "user": "USERNAME",
       "uid": "USER_ID",
       "url": "https://workspace.slack.com/",
    },
    ..
  },
  ..
}

A wiki page should be created to describe the language of the prompt.

Look into RTM implementation

It would be cool to have a nice interface to subscribe to workspace notification via the RTM api. I started a branch feature/rtm, which has a command for connecting to the RTM api and tailing for events in the workspace.

RTM documentation
https://api.slack.com/rtm

Slacker arguments

Implement argument parsing for slacker: --version|-V, --verbose|-v, --help|h

It needs to support "end of arguments" with --, which is only needed if a command is specified afterwards.

Examples:

% slacker --verbose -- files.list --types=gdocs,zips
# Runs single command in verbose mode and exits.

% slacker --verbose
# Runs REPL in verbose mode.

Figure out which API methods to implement

For milestone v0.1, we need to figure out which API methods to include from: https://api.slack.com/methods

The files.list has been implemented but we should probably also get files.info and files.delete.

We also have the emoji.list in script form that we need to convert into a command.

We should actually use api.test whenever we start slacker to test if the Slack API is online and also to tell the user if the internet is not working or similar. This could also be made available as the command "api.test" with aliases "t" and "test" (or maybe without those aliases because of auth.test?).

Whenever a workspace is made active we should query auth.test with the token to test whether it is correct or not. Also do on startup. And in --init whenever a token is entered to try to make sure the user cannot type something that is wrong.

It would probably make sense to implement chat.postMessage so that the users can post messages to channels, but that also requires that we can query and get the ID of a channel by name, like "#general" => "C8VL0UBS5" for our workspace. We can list channels using channels.list.

What do you think, @voidpirate?

Log handler for stdout

Instead of using print, we need to install an stdout log handler with Logger so that all interaction with log file and stdout is using Logger's log levels.

Add --quiet|-q argument to slacker which will then refrain from installing the stdout log handler.

After this is implemented, we need to go through the commands and slacker in general and insert log invocations.

Command base class

The abstract Command base class will make it possible to define command name, aliases, description, help text, possible arguments, and an action(args..) to execute the command.

Later on we need a command registrar in the main script that takes instance of the Command.

Create Commands name syntax

I think we should decide on a common command name syntax, aliases can be whatever but the name itself should follow the Slack API method names.

Examples:
slacker users.list
slacker users.lookUpByName <arg>
slacker usergroups.users.update

Slack seems to keep the commands built on category.type.action. Basically slacker commands should resemble the Slack API methods. This also makes it easier to find and fix bugs with slacker commands and or if the command is calling an older deprecated API method.

cc @netromdk thoughts?

List files

Command: files.list
Aliases: files
Arguments:

Return all files. This will disregard --count and -page but still respects filtering.
--all|-a

Number of items to return per page (defaults to 100).
--count|-c <int>

Page number of results to return (defaults to 1).
--page|-p <int>

Filter files by types, like "snippets,images,pdfs" (defaults to "all").
    all - All files
    spaces - Posts
    snippets - Snippets
    images - Image files
    gdocs - Google docs
    zips - Zip files
    pdfs - PDF files
--types|-p <strs,..>

Show only files with an age in days less than or equal to input amount.
--days-old|-d <int>

Show only files that are older than input amount of days.
--older-than|-o <int>

The defaults should be enforced locally by this command and not rely on what the API does per default, unless it is regarding arguments this command doesn't expose.

API method: https://api.slack.com/methods/files.list

Command Registrar

Implement functionality for registering commands so the REPL knows about it:

  • register(command_instance)
  • find("command name of alias") should return the instance of the command

Command validation

Each command to be registered, must first be validated and if it don't pass an exception must be thrown.

Implement non-abstract Command.validate() in which we can enforce our validations, such as using only [\w\d\.] for command names and aliases, that aliases cannot be a set of one element (because it will then be interpreted as that one element and not a set) etc.

This validation is also valuable when people start contributing commands later on. Then we just require them to run slacker once before submitting a PR.

Session class

The Session is a singleton that only lives for the duration of the program's life cycle.

It should contain the quiet and maybe verbose mode settings. This also means we can avoid the import cycle and strong dependency between Logger and Config. And thus when creating a new log instance it'll ask the session if it is in quiet mode so as not to enable the stream handler.

Extend help command

In lots of programs with subcommands, such as git and lldb, you can write help command_name to get further information. While all commands with an argument parser defined has -h|--help it's not always obvious to the user.

Thus, I propose that we implement the "alias" of help command --> command --help.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.