Release new version 0.8
[grml-paste.git] / grml-paste
index d4b7fb6..b89a200 100755 (executable)
-#!/usr/bin/env ruby
+#!/usr/bin/python3
 # Filename:      grml-paste
-# Purpose:       paste files to the online grml paste bot
-# Authors:       grml-team (grml.org), (c) 2007 Nico Golde <nico@grml.org>
-# Bug-Reports:   see http://grml.org/bugs/
-# License:       This file is licensed under the GPL v2
-##########################################################################
-
-require 'uri'
-require 'net/http'
-
-url = URI.parse('http://paste.grml.org/paste')
-
-def usage
-       $stderr.puts "usage: #{$0} [-n nick] [-s summary] [-f source] [-h|--help]\n"
-       $stderr.puts "\t -n <nick>\tset your nickname (defaults to Anonymuos)"
-       $stderr.puts "\t -s <summary>\tspecify a summary of your paste (defaults to none)"
-       $stderr.puts "\t -f <source>\tsets the file to read from, you don't need this for reading from stdin"
-       $stderr.puts "\t --help\t\tprint this help"
-       Kernel.exit(1)
-end
-
-file    = nil
-summary = "none"
-nick    = "Anonymous"
-channel = "#grml"
-text    = ""
-
-ARGV.each_index do |i|
-       if ARGV[i] == '-h' or ARGV[i] == '--help' then
-               usage
-       elsif ARGV[i] == '-n' and ARGV[i+1] != nil then
-               nick = ARGV[i+1]
-       elsif ARGV[i] == '-s' and ARGV[i+1] != nil then
-               summary = ARGV[i+1]
-       elsif ARGV[i] == '-f' and ARGV[i+1] != nil then
-               file = ARGV[i+1]
-       end
-end
-
-if file == nil or file == "-"
-       f = STDIN
-else
-       begin
-               f = File.open(file)
-       rescue
-               $stderr.puts "Error: couldn't open file: #{file}"
-               Kernel.exit(1)
-       end
-end
-
-f.each_line do |line|
-       text << line
-end
-
-begin
-       res = Net::HTTP.post_form(url,{'Paste it' => 'Paste it', 'paste' => text, 'channel' => channel,  'summary' => summary, 'nick' => nick})
-rescue
-       $stderr.puts "Unable to post HTTP request"
-       Kernel.exit(1)
-end
-
-results = res.body.scan(/<a href='http:\/\/paste.grml.org\/[0-9]+'/)
-result = ""
-if results.size > 0 then
-       result = results[0].sub(/<a href='(http:\/\/paste.grml.org\/[0-9]+)'/, '\1')
-end
-
-if result == "" then
-       print "Failed to paste, please retry\n"
-else
-       print "You can find your paste on: " + result + "\n"
-end
-
-## END OF FILE #################################################################
+# Purpose:       XmlRpc interface client to paste.grml.org
+# Author:        Michael Gebetsroither <gebi@grml.org>
+# License:       This file is licensed under the GPL v2.
+################################################################################
+
+import getpass
+import inspect
+import optparse
+import sys
+from xmlrpc.client import ServerProxy
+
+# program defaults
+DEFAULT_SERVER = "https://paste.grml.org/server.pl"
+
+
+class ActionFailedException(Exception):
+    """Thrown if server returned an error"""
+
+    def __init__(self, errormsg, ret):
+        Exception.__init__(self, errormsg, ret)
+
+    def what(self):
+        """Get errormessage"""
+        return self.args[0]
+
+    def dwhat(self):
+        """Get more verbose errormessage"""
+        return self.args[1]
+
+
+def _paste(opts):
+    """Get paste proxy object"""
+    return ServerProxy(opts.server, verbose=False).paste
+
+
+def _check_success(return_data):
+    """Check if call was successful, raise ActionFailedException otherwise"""
+    if return_data["rc"] != 0:
+        raise ActionFailedException(return_data["statusmessage"], return_data)
+    return return_data
+
+
+def action_add_paste(opts, code):
+    """Add paste to the server: <1.line> <2.line> ...
+
+    default     Read paste from stdin.
+    [text]      Every argument on the commandline will be interpreted as
+                a seperate line of paste.
+    """
+    if len(code) == 0:
+        code = [line.rstrip() for line in sys.stdin.readlines()]
+    code = "\n".join(code)
+    result = _check_success(
+        _paste(opts).addPaste(
+            code, opts.name, opts.expire * 3600, opts.lang, opts.private
+        )
+    )
+    return result["statusmessage"], result
+
+
+def action_del_paste(opts, args):
+    """Delete paste from server: <digest>
+
+    <digest>    Digest of paste you want to remove.
+    """
+    digest = args.pop(0)
+    result = _check_success(_paste(opts).deletePaste(digest))
+    return result["statusmessage"], result
+
+
+def action_get_paste(opts, args):
+    """Get paste from server: <id>
+
+    <id>        Id of paste you want to receive.
+    """
+    paste_id = args.pop(0)
+    result = _check_success(_paste(opts).getPaste(paste_id))
+    return result["code"], result
+
+
+def action_get_langs(opts, args):
+    """Get supported language highlighting types from server"""
+    result = _check_success(_paste(opts).getLanguages())
+    return "\n".join(result["langs"]), result
+
+
+def action_add_short_url(opts, args):
+    """Add short-URL: <url>
+
+    <url>        Short-URL to add
+    """
+    url = args.pop(0)
+    result = _check_success(_paste(opts).addShortURL(url))
+    return result["url"], result
+
+
+def action_get_short_url(opts, args):
+    """Resolve short-URL: <url>
+
+    <url>        Short-URL to get clicks of
+    """
+    url = args.pop(0)
+    result = _check_success(_paste(opts).resolveShortURL(url))
+    return result["url"], result
+
+
+def action_get_short_url_clicks(opts, args):
+    """Get clicks of short-URL: <url>
+
+    <url>        Short-URL to get clicks of
+    """
+    url = args.pop(0)
+    result = _check_success(_paste(opts).ShortURLClicks(url))
+    return result["count"], result
+
+
+def action_help(opts, args):
+    """Print more verbose help about specific action: <action>
+
+    <action>    Topic on which you need more verbose help.
+    """
+    if len(args) < 1:
+        alias = "help"
+    else:
+        alias = args.pop(0)
+
+    if alias in actions:
+        function_name = actions[alias]
+        print(inspect.getdoc(globals()[function_name]))
+        print(
+            "\naliases: "
+            + " ".join([i for i in actions_r[function_name] if i != alias])
+        )
+    else:
+        print("Error: No such command - %s" % (alias))
+        OPT_PARSER.print_usage()
+    sys.exit(0)
+
+
+# action_add_paste -> [add, a]
+actions_r = {}
+
+# add -> action_add_paste
+# a   -> action_add_paste
+actions = {}
+
+# option parser
+OPT_PARSER = None
+
+
+##
+# MAIN
+##
+if __name__ == "__main__":
+    action_specs = [
+        "action_add_paste add a",
+        "action_del_paste del d rm",
+        "action_get_paste get g",
+        "action_get_langs getlangs gl langs l",
+        "action_add_short_url addurl",
+        "action_get_short_url geturl",
+        "action_get_short_url_clicks getclicks",
+        "action_help     help",
+    ]
+    for action_spec in action_specs:
+        aliases = action_spec.split()
+        cmd = aliases.pop(0)
+        actions_r[cmd] = aliases
+    for (action_name, v) in actions_r.items():
+        for i in v:
+            actions[i] = action_name
+
+    usage = (
+        "usage: %prog [options] ACTION <args>\n\n"
+        + "actions:\n"
+        + "\n".join(
+            [
+                "%12s\t%s"
+                % (v[0], inspect.getdoc(globals()[action_name]).splitlines()[0])
+                for (action_name, v) in actions_r.items()
+            ]
+        )
+    )
+    running_user = getpass.getuser()
+    parser = optparse.OptionParser(usage=usage)
+    parser.add_option("-n", "--name", default=running_user, help="Name of poster")
+    parser.add_option(
+        "-e",
+        "--expire",
+        type=int,
+        default=72,
+        metavar="HOURS",
+        help="Time at which paste should expire",
+    )
+    parser.add_option(
+        "-l", "--lang", default="Plain", help="Type of language to highlight"
+    )
+    parser.add_option(
+        "-p",
+        "--private",
+        action="count",
+        dest="private",
+        default=0,
+        help="Create hidden paste",
+    ),
+    parser.add_option("-s", "--server", default=DEFAULT_SERVER, help="Paste server")
+    parser.add_option("-v", "--verbose", action="count", default=0, help="More output")
+    (opts, args) = parser.parse_args()
+    OPT_PARSER = parser
+
+    if len(args) == 0:
+        parser.error("Please provide me with an action")
+    elif args[0] in actions:
+        cmd = args.pop(0)
+        action = actions[cmd]
+        try:
+            (msg, ret) = globals()[action](opts, args)
+            if opts.verbose == 0:
+                print(msg)
+            else:
+                print(ret)
+        except ActionFailedException as except_inst:
+            sys.stderr.write("Server Error: %s\n" % except_inst.what())
+            if opts.verbose > 0:
+                print(except_inst.dwhat())
+            sys.exit(1)
+    else:
+        parser.error("Unknown action: %s" % args[0])