#!/usr/bin/env python
# -*- coding: utf-8 -*-
# nautilus-pastebin - Nautilus extension to paste a file to a pastebin service
# Written by:
# Alessio Treglia
# Copyright (C) 2009, Alessio Treglia
#
# Part of this code has been taken from pastebinit script
# pastebinit was written by:
# Stéphane Graber
# Daniel Bartlett
# David Paleino
# URL: http://www.stgraber.org/download/projects/pastebin/
# Copyright (C) 2006-2009, Stéphane Graber, Daniel Bartlett, David Paleino
# Distributed under the terms of the GNU GPL-2 license
#
# This package is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This package is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this package; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
import urllib
import gtk
import nautilus
import pynotify
import os, sys, re, sys, mimetypes
import gettext
from ConfigParser import ConfigParser
gettext.install("nautilus-pastebin")
# Globals
NAME_APP = "nautilus-pastebin"
CONFIG_FILENAME = 'nautilus-pastebin.conf'
USERCONFIG_RELPATH = '.config/nautilus-pastebin'
PATH_APP = '/usr/share/nautilus-pastebin'
LOGGING = True
class ConfigFileException(Exception):
pass
pynotify.init("Nautilus pastebin extension")
def getConfigurationFilename():
homedir = os.environ.get('HOME')
file_conf = ''
try:
file_conf = os.path.join(homedir, USERCONFIG_RELPATH, CONFIG_FILENAME)
os.stat(file_conf)
except:
file_conf = os.path.join('/etc', CONFIG_FILENAME)
return file_conf
def getPluginsPath():
homedir = os.environ.get('HOME')
plugins_dirs = []
tmp = os.path.join(homedir, USERCONFIG_RELPATH)
if os.path.isdir(tmp):
plugins_dirs.append(tmp)
tmp = os.path.join(PATH_APP, 'plugins')
if os.path.isdir(tmp):
plugins_dirs.append(tmp)
return plugins_dirs
def log(message):
if LOGGING:
if sys.version.split()[0] < '3':
print message
else:
print (message)
# Custom urlopener to handle 401's
class pasteURLopener(urllib.FancyURLopener):
def http_error_401(self, url, fp, errcode, errmsg, headers, data=None):
return None
class ConfigurationValidator(object):
"""Configuration wrapper """
NEEDED_OPTS = ['pastebin']
def validate(self):
log (_("Validating configuration file..."))
conf = {}
opts = self.__config.options('generals')
# Each needed field must be fill'd.
if not set(ConfigurationValidator.NEEDED_OPTS).issubset(set(opts)):
raise ConfigFileException, _("Missing some required values in the configuration file.")
for k in opts:
conf[k] = self.__config.get('generals', k)
if k in ConfigurationValidator.NEEDED_OPTS and conf[k] == '':
raise ConfigFileException, _("Missing some required values in the configuration file.")
# Everything seems well-configured
for k,v in conf.items():
setattr(self,k,v)
return True
def getPastebinConfiguration(self):
"""Retrieve and returns the configuration related to the pastebin service."""
pastebinConfiguration = None
if self.__config.has_section(self.pastebin):
pastebinConfiguration = PastebinConfiguration(
dict(self.__config.items(self.pastebin)) )
return pastebinConfiguration
def reloadConfiguration(self):
"""Reload the configuration file"""
log (_("Reloading configuration..."))
self.__config.read(self.filename)
def __init__(self, filename='nautilus-pastebin.conf'):
log (_("Initializing global configuration validator..."))
self.filename = filename
self.__config = ConfigParser()
self.__config.read(filename)
class PastebinConfiguration(object):
PARAMETERS = ["website",
"content",
"user",
"jabberid",
"version",
"format",
"parentpid",
"permatag",
"title",
"username",
"password"]
def __init__(self, optionsDict):
log (_("Initializing pastebin configuration..."))
# Initialize all parameters
for k in PastebinConfiguration.PARAMETERS:
setattr(self,k,'')
# Read the configuration and set the parameters
for k,v in optionsDict.items():
setattr(self,k,v)
# Initializing formatMap...
formatMap = dict(map(lambda k: (k,''),
filter(lambda x: x.startswith('text'), mimetypes.types_map.values())))
for i in formatMap.keys(): # Set 'text' as default format
formatMap[i] = 'text'
# Read the custom types list from the configuration
exec(optionsDict['mimetypes'])
self.mimetypes = formatMap
def setFormatByMimeType(self, mimetype):
try:
self.format = self.mimetypes[mimetype]
except KeyError:
return False # unsupported mimetype
return True
def __str__(self):
s = 'Configuration:\n'
for k in PastebinConfiguration.PARAMETERS:
s += "%(k)s = %(v)s\n" % {'k' : k, 'v': getattr(self, k)}
s += "mimetypes = %s" % self.mimetypes.__str__()
return s
class PastebinValidatorFactory(object):
"""Singleton factory which returns a validator object dinamically."""
__instance = None
class __impl(object):
"""Implementation of the class' methods"""
def getValidator(self, pastebinConfiguration):
website = pastebinConfiguration.website
log (_("Looking for a validator: %s") %website)
if (re.search("http://((([a-zA-Z0-9\-_\.]*)(pastebin\.com)))", website) and not website == "http://www.pastebin.com") or website == "http://pastebin.mozilla.org":
return PastebinComValidator(pastebinConfiguration)
elif re.search("http://((([a-zA-Z0-9\-_\.]*)(pastebin\.ca)))", website):
return PastebinCaValidator(pastebinConfiguration)
elif website == "http://rafb.net":
return RafbNetValidator(pastebinConfiguration)
elif website == "http://stikked.com":
return StikkedComValidator(pastebinConfiguration)
elif website == "http://slexy.org":
return SlexyOrgValidator(pastebinConfiguration)
elif website == "http://fpaste.org":
return FpasteValidator(pastebinConfiguration)
elif website == "http://paste2.org":
return Paste2OrgValidator(pastebinConfiguration)
elif re.search("http://((([a-zA-Z0-9\-_\.]*)(pastey\.net)))", website):
return PasteyNetValidator(pastebinConfiguration)
elif website == "http://yourpaste.net":
return YourpasteNetValidator(pastebinConfiguration)
elif website == "http://gist.github.com":
return GistGithubComValidator(pastebinConfiguration)
elif website == "http://paste.ubuntu.com":
return PasteUbuntuComValidator(pastebinConfiguration)
elif website == "http://paste.debian.net":
return PasteDebianNetValidator(pastebinConfiguration)
else:
return None # Service has not been recognized
def __init__(self):
if PastebinValidatorFactory.__instance is None:
PastebinValidatorFactory.__instance = PastebinValidatorFactory.__impl()
self.__dict__['_PastebinValidatorFactory__instance'] = PastebinValidatorFactory.__instance
def __getattr__(self, attr):
return getattr(self.__instance, attr)
def __setattr__(self, attr, value):
return setattr(self.__instance, attr, value)
class GenericPastebinValidator(object):
NOT_EMPTY_FIELDS = ['website',
'content']
def __init__(self, pastebinConfiguration):
if not self.__class__.__name__ == 'GenericPastebinValidator':
self.conf = pastebinConfiguration
else:
raise NotImplementedError
def validate(self):
if not self.__class__.__name__ == 'GenericPastebinValidator':
if not self.checkNotEmpty(['content', 'website']):
return False
if self.conf.user == '':
self.conf.user = os.environ.get('USER')
return True
else:
raise NotImplementedError
def checkNotEmpty(self, opts):
if not self.__class__.__name__ == 'GenericPastebinValidator':
try:
for i in GenericPastebinValidator.NOT_EMPTY_FIELDS:
if getattr(self.conf, i) == '':
raise Exception
except:
return False
return True
else:
raise NotImplementedError
def getParameters(self): raise NotImplementedError
class PasteUbuntuComValidator(GenericPastebinValidator):
def validate(self):
PasteUbuntuComValidator.NOT_EMPTY_FIELDS.append('format')
return GenericPastebinValidator.validate(self)
def getParameters(self):
params = {}
params['poster'] = self.conf.user
params['syntax'] = self.conf.format
params['content'] = self.conf.content
return params
class PasteDebianNetValidator(GenericPastebinValidator):
def validate(self):
PasteDebianNetValidator.NOT_EMPTY_FIELDS.append('format')
return GenericPastebinValidator.validate(self)
def getParameters(self):
params = {}
params['poster'] = self.conf.user
params['lang'] = self.conf.format # "-1" The format, for syntax hilighting, default to plain text
#params['syntax'] = self.conf.format #The format, for syntax hilighting
params['code'] = self.conf.content
params['remember'] = "0" #Do you want a cookie ?
params['expire'] = "259200" # expire in 72h
return params
class GistGithubComValidator(GenericPastebinValidator):
def validate(self):
GistGithubComValidator.NOT_EMPTY_FIELDS.extend(['format', 'title'])
return GenericPastebinValidator.validate(self)
def getParameters(self):
params = {}
params['page'] = "/gists"
params['poster'] = self.conf.user
params['file_ext[gistfile1]'] = self.conf.format #The format, for syntax hilighting
params['file_contents[gistfile1]'] = self.conf.content
params['file_name[gistfile1]'] = self.conf.title
return params
class YourpasteNetValidator(GenericPastebinValidator):
def validate(self):
YourpasteNetValidator.NOT_EMPTY_FIELDS.extend(['format', 'title'])
return GenericPastebinValidator.validate(self)
def getParameters(self):
params = {}
params['syntax'] = self.conf.format
params['name'] = self.conf.user
params['desc'] = self.conf.title
params['expire'] = "0" # Forever
params['code'] = self.conf.content
params['private'] = "0" # It's not a private post
params['remember'] = "0" # Cookies? ;)
params['page'] = "/paste"
params['regexp'] = '">http://yourpaste.net(.*)/'
return params
class PasteyNetValidator(GenericPastebinValidator):
def parentFixup(self, website, parentid):
if parentid == "":
return ""
url_opener = pasteURLopener()
page = url_opener.open(website + '/' + parentid, None)
matches = re.split('http://(?:(?:[a-zA-Z0-9\-_\.]*)(?:pastey\.net))(/.*)'
return params
class PastebinComValidator(GenericPastebinValidator):
def validate(self):
PastebinComValidator.NOT_EMPTY_FIELDS.extend(['format','version'])
return GenericPastebinValidator.validate(self)
def getParameters(self):
params = {}
params['poster'] = self.conf.user
params['code2'] = self.conf.content
params['version'] = self.conf.version
params['parent_pid'] = self.conf.parentpid #For reply, "" means homepage (new thread)
params['format'] = self.conf.format #The format, for syntax hilighting
params['paste'] = "Send"
params['remember'] = "0" #Do you want a cookie ?
params['expiry'] = "f" #The expiration, f = forever
return params
class PastebinCaValidator(GenericPastebinValidator):
def getParameters(self):
params = {}
params['name'] = self.conf.user
params['content'] = self.conf.content
params['type'] = "1" #The expiration, 1 = raw
params['save'] = "0" #Do you want a cookie ?
params['s'] = "Submit Post"
params['regexp'] = '">http://.*pastebin.ca/(.*)
'
return params
class RafbNetValidator(GenericPastebinValidator):
def validate(self):
RafbNetValidator.NOT_EMPTY_FIELDS.append('format')
return GenericPastebinValidator.validate(self)
def getParameters(self):
params = {}
params['page'] = "/paste/paste.php"
params['nick'] = self.conf.user
params['text'] = self.conf.content
params['lang'] = "Plain Text" #The format, for syntax hilighting
params['cvt_tabs'] = "No"
return params
class StikkedComValidator(GenericPastebinValidator):
def validate(self):
StikkedComValidator.NOT_EMPTY_FIELDS.extend(['title', 'format'])
return GenericPastebinValidator.validate(self)
def getParameters(self):
params = {}
params['name'] = self.conf.user
params['title'] = self.conf.title
params['email'] = ""
params['lang'] = self.conf.format #"text" #The format, for syntax hilighting
params['code'] = self.conf.content
params['website'] = ""
params['submit'] = "submit"
return params
class SlexyOrgValidator(GenericPastebinValidator):
def validate(self):
SlexyOrgValidator.NOT_EMPTY_FIELDS.extend(['title', 'format'])
return GenericPastebinValidator.validate(self)
def getParameters(self):
params = {}
params['page'] = "/index.php/submit"
params['raw_paste'] = self.conf.content
params['comment'] = ""
params['author'] = self.conf.user
params['language'] = self.conf.format #"text" #The format, for syntax hilighting
params['permissions'] = "0"
params['desc'] = self.conf.title
params['linenumbers'] = "0"
params['expire'] = "0"
params['submit'] = "Submit Paste"
return params
class FpasteValidator(GenericPastebinValidator):
def validate(self):
FpasteValidator.NOT_EMPTY_FIELDS.append('format')
return GenericPastebinValidator.validate(self)
def getParameters(self):
params = {}
params['lang'] = self.conf.format #The format, for syntax hilighting
params['page'] = "/paste/save"
params['content'] = self.conf.content
params['submit'] = "nautilus-pastebin"
return params
class Paste2OrgValidator(GenericPastebinValidator):
def validate(self):
Paste2OrgValidator.NOT_EMPTY_FIELDS.extend(['format','title'])
return GenericPastebinValidator.validate(self)
def getParameters(self):
params = {}
params['page'] = "/new-paste"
params['description'] = self.conf.title
params['lang'] = self.conf.format
params['code'] = self.conf.content
params['parent'] = "0"
return params
class ValidatorPluginLoader(object):
"""Experimental feature"""
@staticmethod
def getValidatorPlugin(name):
sys.path.extend(getPluginsPath())
obj = None
stmt = """
from %(name)s import %(name)sValidator
obj = %(name)sValidator
"""
exec(stmt % {'name' : name})
return obj
def __init__(self): raise NotImplementedError
class PastebinitExtension(nautilus.MenuProvider):
def __init__(self):
log ("Intializing Nautilus Pastebin extension...")
def get_file_items(self, window, files):
if len(files)!=1:
return
filename = files[0]
validator = ConfigurationValidator(getConfigurationFilename())
validator.validate()
pasteconf = validator.getPastebinConfiguration()
if not pasteconf.setFormatByMimeType(filename.get_mime_type()):
return
self.pasteconf = pasteconf
items = []
#Called when the user selects a file in Nautilus.
item = nautilus.MenuItem("NautilusPython::pastebin_item",
_("Pastebin"),
_("Pastebin this file"))
item.set_property('icon', "nautilus-pastebin")
item.connect("activate", self.menu_activate_cb, files)
items.append(item)
return items
def menu_activate_cb(self, menu, files):
if len(files) != 1:
return
filename = files[0]
if filename.get_uri_scheme() != 'file':
return
if filename.is_directory():
return
pastefile = urllib.unquote(filename.get_uri()[7:])
log (_("Trying to paste %s") % pastefile)
fp = open(pastefile)
content = fp.read()
fp.close()
# OK, let's go...
self.pasteconf.content = content
validator = PastebinValidatorFactory().getValidator(self.pasteconf)
if not validator.validate():
return
website = self.pasteconf.website
params = validator.getParameters()
if not re.search(".*/", website):
website += "/"
reLink=None
tmp_page=""
if params.__contains__("page"):
website+=params['page']
tmp_page=params['page']
params.__delitem__("page")
if params.__contains__("regexp"):
reLink=params['regexp']
params.__delitem__("regexp")
params = urllib.urlencode(params) #Convert to a format usable with the HTML POST
try:
url_opener = pasteURLopener()
page = url_opener.open(website, params) #Send the informations and be redirected to the final page
summary = 'File pasted to: '
pasteurl = '%(pasteurl)s'
if reLink: #Check if we have to apply a regexp
website = website.replace(tmp_page, "")
message = pasteurl % { 'pasteurl' :
website + "/" + re.split(reLink, page.read())[1] }
else:
message = pasteurl % { 'pasteurl': page.url }
except:
summary = _("Unable to read or parse the result page")
message = _("It could be a server timeout or a change server side, try later.")
finally:
n = pynotify.Notification(summary, message)
n.show()