diff --git a/README.md b/README.md index 8057511..8ad0879 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,10 @@ Geany Infrastructure Scripts ============================ - The scripts in this repository are used for various purposes on geany.org. -Basically they provide some additional and cool functions like announcing -GIT commits to the IRC channel, maintain the GIT mirror repository and similar tasks. - +Basically they provide some additional and cool functions like generating +GIT commits emails, maintain the GIT mirror repository and similar tasks. Add or remove a repository ========================== @@ -14,7 +12,6 @@ Add or remove a repository If you want to add or remove a repository maintained by these scripts, follow these steps: * Edit the following files and find relevant repository information at the head of each file: - * scripts/git2irc/git2irc.conf * scripts/git_hooks/github_commit_mail.py * scripts/git_hooks/post_commit_hook.py @@ -22,9 +19,6 @@ If you want to add or remove a repository maintained by these scripts, follow th cd /home/geany/infrastructure && git pull - * Edit /home/geany/git2irc.conf on geany:org: add/remove the repository from the - "repositories" settings at the top of the file - * Edit /usr/local/cgit/cgitrc on geany.org: at the end of the file, copy one of the existing repository stanzas and adjust the settings accordingly @@ -66,16 +60,6 @@ These scripts are used for the nightly builds, for details see [builders/README.md](builders/README.md). -IRC Bot Plugins -=============== - -In the directory ircbot-plugins there are two plugins for the IRC bot Supybot -(http://www.supybot.org/). - -The plugins enhance the used Supybot instance on #geany by various useful and funny -features like a bunch of !commands. For details, read the source code. - - License ======= Unless stated otherwise all code in this repository is licensed of under the terms diff --git a/ircbot-plugins/Geany/README.txt b/ircbot-plugins/Geany/README.txt deleted file mode 100644 index a30a3ed..0000000 --- a/ircbot-plugins/Geany/README.txt +++ /dev/null @@ -1,5 +0,0 @@ -Miscellaneous fun commands we used to have in #geany on Freenode.net. - -The code is mostly a big hack and not generally usable. -It has hard-coded paths and quite special functionalities. Still, -feel free to use and/or modify it as necessary. diff --git a/ircbot-plugins/Geany/__init__.py b/ircbot-plugins/Geany/__init__.py deleted file mode 100644 index 4375cfe..0000000 --- a/ircbot-plugins/Geany/__init__.py +++ /dev/null @@ -1,61 +0,0 @@ -### -# -*- coding: utf-8 -*- -# Copyright (c) 2003-2005, Jeremiah Fincher -# Copyright (c) 2010, Enrico Tröger -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, -# this list of conditions, and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions, and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of the author of this software nor the name of -# contributors to this software may be used to endorse or promote products -# derived from this software without specific prior written consent. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. -### - -""" -Provides various fun commands. -""" - -import supybot -import supybot.world as world - -# Use this for the version of this plugin. You may wish to put a CVS keyword -# in here if you're keeping the plugin in CVS or some similar system. -__version__ = "1.0" - -__author__ = supybot.Author('Enrico Tröger', 'eht16', - 'enrico.troeger@uvena.de') - -# This is a dictionary mapping supybot.Author instances to lists of -# contributions. -__contributors__ = {} - -import config -import plugin -reload(plugin) # In case we're being reloaded. -# Add more reloads here if you add third-party modules and want them to be -# reloaded when this plugin is reloaded. Don't forget to import them as well! - - -Class = plugin.Class -configure = config.configure - - -# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/ircbot-plugins/Geany/config.py b/ircbot-plugins/Geany/config.py deleted file mode 100644 index 148b4ae..0000000 --- a/ircbot-plugins/Geany/config.py +++ /dev/null @@ -1,46 +0,0 @@ -### -# -*- coding: utf-8 -*- -# Copyright (c) 2003-2005, Jeremiah Fincher -# Copyright (c) 2010, Enrico Tröger -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, -# this list of conditions, and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions, and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of the author of this software nor the name of -# contributors to this software may be used to endorse or promote products -# derived from this software without specific prior written consent. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. -### - -import supybot.conf as conf -import supybot.registry as registry - -def configure(advanced): - # This will be called by supybot to configure this module. advanced is - # a bool that specifies whether the user identified himself as an advanced - # user or not. You should effect your configuration by manipulating the - # registry as appropriate. - conf.registerPlugin('Geany', True) - - -Geany = conf.registerPlugin('Geany') - - -# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/ircbot-plugins/Geany/plugin.py b/ircbot-plugins/Geany/plugin.py deleted file mode 100644 index 5fc4de1..0000000 --- a/ircbot-plugins/Geany/plugin.py +++ /dev/null @@ -1,315 +0,0 @@ -### -# -*- coding: utf-8 -*- -# Copyright (c) 2003-2005, Jeremiah Fincher -# Copyright (c) 2010, Enrico Tröger -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, -# this list of conditions, and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions, and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of the author of this software nor the name of -# contributors to this software may be used to endorse or promote products -# derived from this software without specific prior written consent. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. -### - -from timer import RepeatTimer - -import random -import threading -from ConfigParser import SafeConfigParser - -from supybot.commands import * -import supybot.callbacks as callbacks - -import supybot.conf as conf - - -IRC_USERLIST_FILEPATH = '/srv/tmp/irc_userlist' -GOODIES = { - 'coffee': 'A nice waiter brings %s a big cup of coffee!', - 'coke': 'A nice waiter brings %s a cool bottle of coke!', - 'pepsi': 'A nice waiter brings %s a cool bottle of Pepsi!', - 'juice': 'A nice waiter brings %s a glass of fresh juice!', - 'vodka': 'A nice waiter brings %s a shot glass of vodka!', - 'beer': 'A nice waiter brings %s a nice bottle of beer!', - 'tea': 'A nice waiter brings %s a cup of hot tea!', - 'milk': 'A nice waiter brings %s a glass of fresh, cold milk', - 'chocolate': 'A nice waiter brings %s a piece of sweet chocolate', - 'pizza': 'Someone calls Mario, and he brings %s a tasty hawaiian pizza!' -} - - -class Geany(callbacks.Plugin): - def __init__(self, irc): - self.__parent = super(Geany, self) - self.__parent.__init__(irc) - self.timer = RepeatTimer(180.0, self._write_user_list, self.log, 0, [irc]) - self.timer.start() - self.help_database = {} - self._read_help_database() - - def die(self): - if self.timer: - threading.Thread(target=self.timer.cancel).start() - - def _read_help_database(self): - config = SafeConfigParser() - config.read('help_database.rc') - for key, value in config.items('general'): - self.help_database[key] = value - - def _write_help_database(self): - conv = lambda dic: ['%s: %s' % (k, v) for (k, v) in dic.iteritems()] - data = conv(self.help_database) - data.sort() - f = open('help_database.rc', 'w') - f.write('[general]\n') - f.write('\n'.join(data)) - f.close() - - def _write_user_list(self, irc): - exclude_nicks = ['ChanServ', self._get_nick_name()] - - def filter_services(value): - return value not in exclude_nicks - - if hasattr(irc, 'getRealIrc'): - state = irc.getRealIrc().state - elif hasattr(irc, 'state'): - state = irc.state - else: - state = None - - if state: - channel_users = state.channels['#geany'].users - # filter myself and ChanServ - users = filter(filter_services, channel_users) - - f = open(IRC_USERLIST_FILEPATH, 'w') - f.write('\n'.join(users)) - f.close() - - def _get_nick_name(self): - """ - Return the configured nick name - """ - return str(conf.supybot.nick) - #~ return self.registryValue('nick') - - def _get_command_name(self, msg, fallback='help'): - """ - Parse and return the actual command name - """ - try: - cmd = msg.args[1].split()[0] - if cmd[0] == '!': - cmd = cmd[1:] - except: - cmd = fallback - return cmd - - def _process_help_request(self, irc, text): - if text == 'keywords': - keywords = sorted(self.help_database.keys()) - irc.reply(' '.join(keywords)) - return - - try: - result = self.help_database[text] - if result: - while result[0] == '@': - # read alias - # (The outer while loop could easily cause endless lookups if there are - # circular aliases defined, let's hope users stay nice.) - result = self.help_database[result[1:]] - irc.reply(result) - except KeyError: - pass - - def doPrivmsg(self, irc, msg): - (recipients, text) = msg.args - if text.startswith('?? '): - self._process_help_request(irc, text[3:]) - - def goodie(self, irc, msg, args, text): - """takes no arguments - - Request a goodie - """ - if not text: - rcpt = msg.nick - else: - text = text[0].split() - if len(text) > 1: - if text[0] == 'for': - if text[1] == 'me': - rcpt = msg.nick - else: - rcpt = text[1] - else: - rcpt = text[0] - else: - rcpt = text[0] - - cmd = self._get_command_name(msg, 'tea') - try: - irc.reply(GOODIES[cmd] % rcpt) - except KeyError: - pass - - def listgoodies(self, irc, msg, args, text): - """takes no arguments - - Lists available goodies - """ - available_goodies = sorted(GOODIES.keys()) - available_goodies = ', '.join(available_goodies) - text = 'A nice waiter offers the following goodies for you: %s' % available_goodies - irc.reply(text) - - def hello(self, irc, msg, args, text): - """takes no arguments - - Greetings - """ - text = 'Hi %s. My name is %s and I\'m here to offer additional services to you! Try \"?? help\" for general information.' % (msg.nick, self._get_nick_name()) - irc.reply(text) - - def thanks(self, irc, msg, args, text): - """takes no arguments - - Thanks - """ - text = '%s, no problem. It was a pleasure to serve you.' % (msg.nick) - irc.reply(text) - - def test(self, irc, msg, args, text): - """takes no arguments - - Bah, tests - """ - irc.reply('I don\'t like tests!') - - def _learn(self, key, value): - - update = key in self.help_database - - self.help_database[key] = value - - self._write_help_database() - - return update - - def learn(self, irc, msg, args, key, value): - """newKeyword Text... - - With the command !learn you can add new keywords to the database. - Use "!learn newKeyword Text which should be added" to add new keywords. - Use this with care! - """ - update = self._learn(key, value) - - if update: - irc.reply('Existing keyword "%s" was updated' % key) - else: - irc.reply('New keyword "%s" was added' % key) - - def alias(self, irc, msg, args, dest, source): - """newWord existingWord - - Type '!alias newWord existingWord' to create a new alias, e.g. '!alias svn subversion'. - """ - if not source in self.help_database: - irc.reply('Alias "%s" could not be created because the target does not exist' % dest) - return - - update = self._learn(dest, '@%s' % source) - - if update: - irc.reply('Existing alias "%s" was updated' % dest) - else: - irc.reply('New alias "%s" was added' % dest) - - def moo(self, irc, msg, args): - """takes no arguments - - Have you mooed today? - """ - if random.randrange(0, 2): - text = """ ^__^ - (oo) - /-----(__) - / | || - * /\\---/\\ - ~~ ~~ -.."Have you mooed today?"..""" - for line in text.split('\n'): - irc.reply(line) - else: - irc.reply('I have Super Cow Powers. Have you mooed today?') - - def commit(self, irc, msg, args, idx): - """takes one argument, a Git ID SHA - - Type '!commit ' to print a URL/link to view the commit - in Geany's online Git repository browser. - """ - idx = str(idx).lower().strip() - if all(ch in 'abcdef0123456789' for ch in idx): - irc.reply('https://github.com/geany/geany/commit/' + idx) - # using Github since it allows shortened SHAs also - #irc.reply('http://git.geany.org/geany/commit/?id=' + idx) - else: - irc.reply('Malformed Git SHA') - - # "decorate" our commands (wrap is a decorator replacement for old Python versions) - tea = wrap(goodie, [optional(many('text'))]) - coffee = wrap(goodie, [optional(many('text'))]) - coke = wrap(goodie, [optional(many('text'))]) - pepsi = wrap(goodie, [optional(many('text'))]) - juice = wrap(goodie, [optional(many('text'))]) - vodka = wrap(goodie, [optional(many('text'))]) - beer = wrap(goodie, [optional(many('text'))]) - pizza = wrap(goodie, [optional(many('text'))]) - chocolate = wrap(goodie, [optional(many('text'))]) - milk = wrap(goodie, [optional(many('text'))]) - goodies = wrap(listgoodies, [optional(many('text'))]) - goods = wrap(listgoodies, [optional(many('text'))]) - - hi = wrap(hello, [optional(many('text'))]) - hello = wrap(hello, [optional(many('text'))]) - hey = wrap(hello, [optional(many('text'))]) - - thanks = wrap(thanks, [optional(many('text'))]) - thankyou = wrap(thanks, [optional(many('text'))]) - thx = wrap(thanks, [optional(many('text'))]) - - learn = wrap(learn, ['something', 'text']) - alias = wrap(alias, ['something', 'something']) - - test = wrap(test, [optional(many('text'))]) - moo = wrap(moo) - commit = wrap(commit, ['text']) - - -Class = Geany - - -# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/ircbot-plugins/Geany/timer.py b/ircbot-plugins/Geany/timer.py deleted file mode 100644 index facdc4d..0000000 --- a/ircbot-plugins/Geany/timer.py +++ /dev/null @@ -1,59 +0,0 @@ -### -# -*- coding: utf-8 -*- -# Copyright (c) 2003-2005, Jeremiah Fincher -# Copyright (c) 2010, Enrico Tröger -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, -# this list of conditions, and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions, and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of the author of this software nor the name of -# contributors to this software may be used to endorse or promote products -# derived from this software without specific prior written consent. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. -### - -from threading import Event, Thread - - -class RepeatTimer(Thread): - def __init__(self, interval, function, logger, iterations=0, args=[], kwargs={}): - Thread.__init__(self) - self.interval = interval - self.function = function - self.iterations = iterations - self.args = args - self.kwargs = kwargs - self.finished = Event() - self.logger = logger - - def run(self): - count = 0 - self.logger.info(u'start') - while not self.finished.isSet() and (self.iterations <= 0 or count < self.iterations): - self.finished.wait(self.interval) - if not self.finished.isSet(): - self.function(*self.args, **self.kwargs) - count += 1 - - def cancel(self): - self.logger.info(u'cancelled') - self.finished.set() - - diff --git a/ircbot-plugins/SupySocketServer/README.txt b/ircbot-plugins/SupySocketServer/README.txt deleted file mode 100644 index 441170f..0000000 --- a/ircbot-plugins/SupySocketServer/README.txt +++ /dev/null @@ -1,4 +0,0 @@ - -External Control - -Version: 1.0 diff --git a/ircbot-plugins/SupySocketServer/__init__.py b/ircbot-plugins/SupySocketServer/__init__.py deleted file mode 100644 index bec6053..0000000 --- a/ircbot-plugins/SupySocketServer/__init__.py +++ /dev/null @@ -1,66 +0,0 @@ -### -# -*- coding: utf-8 -*- -# Copyright (c) 2007, Ali Afshar -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, -# this list of conditions, and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions, and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of the author of this software nor the name of -# contributors to this software may be used to endorse or promote products -# derived from this software without specific prior written consent. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. - -### - -""" -Add a description of the plugin (to be presented to the user inside the wizard) -here. This should describe *what* the plugin does. -""" - -import supybot -import supybot.world as world - -# Use this for the version of this plugin. You may wish to put a CVS keyword -# in here if you're keeping the plugin in CVS or some similar system. -__version__ = "" - -# XXX Replace this with an appropriate author or supybot.Author instance. -__author__ = supybot.Author('Enrico Tröger', 'eht16', - 'enrico.troeger@uvena.de') - -# This is a dictionary mapping supybot.Author instances to lists of -# contributions. -__contributors__ = {} - -# This is a url where the most recent plugin package can be downloaded. -__url__ = '' # 'http://supybot.com/Members/yourname/ExternalControl/download' - -import config -import plugin -reload(plugin) # In case we're being reloaded. -# Add more reloads here if you add third-party modules and want them to be -# reloaded when this plugin is reloaded. Don't forget to import them as well! - - -Class = plugin.Class -configure = config.configure - - -# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: diff --git a/ircbot-plugins/SupySocketServer/config.py b/ircbot-plugins/SupySocketServer/config.py deleted file mode 100644 index 58324e9..0000000 --- a/ircbot-plugins/SupySocketServer/config.py +++ /dev/null @@ -1,58 +0,0 @@ -### -# Copyright (c) 2007, Ali Afshar -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, -# this list of conditions, and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions, and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of the author of this software nor the name of -# contributors to this software may be used to endorse or promote products -# derived from this software without specific prior written consent. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. - -### - -import supybot.conf as conf -import supybot.registry as registry - -def configure(advanced): - # This will be called by supybot to configure this module. advanced is - # a bool that specifies whether the user identified himself as an advanced - # user or not. You should effect your configuration by manipulating the - # registry as appropriate. - conf.registerPlugin('SupySocketServer', True) - - -SupySocketServer = conf.registerPlugin('SupySocketServer') -# This is where your configuration variables (if any) should go. For example: -# conf.registerGlobalValue(ExternalControl, 'someConfigVariableName', -# registry.Boolean(False, """Help for someConfigVariableName.""")) - -conf.registerGlobalValue(SupySocketServer, 'port', - registry.Integer(7766, - """The port that the external control server - should wait for requests on.""")) - -conf.registerGlobalValue(SupySocketServer, 'host', - registry.String('localhost', - """The host that the external control server - should wait for requests on.""")) - - -# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: diff --git a/ircbot-plugins/SupySocketServer/plugin.py b/ircbot-plugins/SupySocketServer/plugin.py deleted file mode 100644 index 7e8cb60..0000000 --- a/ircbot-plugins/SupySocketServer/plugin.py +++ /dev/null @@ -1,245 +0,0 @@ -### -# Copyright (c) 2007, Ali Afshar -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, -# this list of conditions, and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions, and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# * Neither the name of the author of this software nor the name of -# contributors to this software may be used to endorse or promote products -# derived from this software without specific prior written consent. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. - -### - -import select -import threading -import socket -import SocketServer - - -import supybot.world as world -from supybot.commands import * -from supybot.log import getPluginLogger -import supybot.ircmsgs as ircmsgs -import supybot.callbacks as callbacks - - - -class RequestHandler(SocketServer.StreamRequestHandler): - - def handle(self): - # data should be: 'network channel message', e.g. - # 'Freenode #geany blah blah' - data = self.rfile.readline().strip() - self.server.logger.debug(u'got data from socket: %s' % data) - network, channel, message = data.split(' ', 2) - ci = ControlInstance() - ci.privmsg(network, channel, message) - - -class ControlInstance(object): - - def privmsg(self, network, personorchannel, message): - target_irc, target = self._get_irc_and_target(network, personorchannel) - msg = ircmsgs.privmsg(target, message) - target_irc.sendMsg(msg) - - def _get_irc(self, network): - for irc in world.ircs: - if irc.network == network: - return irc - - def _get_person_or_channel(self, irc, personorchannel): - if personorchannel.startswith('#'): - for channel in irc.state.channels: - if channel == personorchannel: - return channel - else: - return personorchannel - - def _get_irc_and_target(self, network, personorchannel): - target_irc = self._get_irc(network) - if target_irc is None: - raise Exception('Not on Network: %s' % network) - target = self._get_person_or_channel(target_irc, personorchannel) - if target is None: - raise Exception('Not on Channel: %s' % personorchannel) - return target_irc, target - - -class SocketServerImpl(SocketServer.TCPServer): - - timeout = None - allow_reuse_address = True - address_family = socket.AF_INET - socket_type = socket.SOCK_STREAM - request_queue_size = 5 - - def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True): - """Constructor. May be extended, do not override.""" - SocketServer.BaseServer.__init__(self, server_address, RequestHandlerClass) - self.socket = socket.socket(self.address_family, self.socket_type) - self.__is_shut_down = threading.Event() - self.__serving = False - if bind_and_activate: - self.server_bind() - self.server_activate() - - def server_bind(self): - """Called by constructor to bind the socket. - - May be overridden. - - """ - if self.allow_reuse_address: - self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self.socket.bind(self.server_address) - self.server_address = self.socket.getsockname() - - def server_activate(self): - """Called by constructor to activate the server. - - May be overridden. - - """ - self.socket.listen(self.request_queue_size) - - def serve_forever(self, poll_interval=0.5): - """Handle one request at a time until shutdown. - - Polls for shutdown every poll_interval seconds. Ignores - self.timeout. If you need to do periodic tasks, do them in - another thread. - """ - self.logger.info(u'listen') - self.__serving = True - self.__is_shut_down.clear() - while self.__serving: - # XXX: Consider using another file descriptor or - # connecting to the socket to wake this up instead of - # polling. Polling reduces our responsiveness to a - # shutdown request and wastes cpu at all other times. - r = select.select([self], [], [], poll_interval)[0] - if r: - self._handle_request_noblock() - self.__is_shut_down.set() - self.logger.info(u'sucessfully shut down') - - def shutdown(self): - """Stops the serve_forever loop. - - Blocks until the loop has finished. This must be called while - serve_forever() is running in another thread, or it will - deadlock. - """ - self.logger.info(u'shutdown called') - self.__serving = False - self.__is_shut_down.wait() - - def _handle_request_noblock(self): - """Handle one request, without blocking. - - I assume that select.select has returned that the socket is - readable before this function was called, so there should be - no risk of blocking in get_request(). - """ - try: - request, client_address = self.get_request() - except socket.error: - return - if self.verify_request(request, client_address): - try: - self.process_request(request, client_address) - except: - self.handle_error(request, client_address) - self.close_request(request) - - def handle_request(self): - """Handle one request, possibly blocking. - - Respects self.timeout. - """ - # Support people who used socket.settimeout() to escape - # handle_request before self.timeout was available. - timeout = self.socket.gettimeout() - if timeout is None: - timeout = self.timeout - elif self.timeout is not None: - timeout = min(timeout, self.timeout) - fd_sets = select.select([self], [], [], timeout) - if not fd_sets[0]: - self.handle_timeout() - return - self._handle_request_noblock() - - -class SupySocketServer(callbacks.Plugin): - """Add the help for "@plugin help SupySocketServer" here - This should describe *how* to use this plugin.""" - threaded = True - - def __init__(self, irc): - callbacks.Plugin.__init__(self, irc) - self._server = None - self._server_thread = None - self._start_server_in_thread() - - def _start_server_in_thread(self): - self._server_thread = threading.Thread(target=self._start_server) - self._server_thread.daemon = True - self._server_thread.start() - - def _start_server(self): - host = self.registryValue('host') - port = self.registryValue('port') - self._server = SocketServerImpl((host, port), RequestHandler) - self._server.logger = self.log - self._server.serve_forever() - - def die(self): - if self._server: - self._server.shutdown() - self._server_thread.join() - - - def outFilter(self, irc, msg): - if msg.inReplyTo: - if msg.inReplyTo.supysocketserver: - target_irc, target, notice = msg.inReplyTo.supysocketserver - self._reply_command(target_irc, target, msg, notice) - return None - else: - return msg - else: - return msg - - def _reply_command(self, target_irc, target, msg, notice): - if notice: - factory = ircmsgs.notice - else: - factory = ircmsgs.privmsg - reply_msg = factory(target, msg.args[1]) - target_irc.sendMsg(reply_msg) - - -Class = SupySocketServer - - -# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: diff --git a/scripts/git2irc/git2irc.conf b/scripts/git2irc/git2irc.conf deleted file mode 100644 index 297b764..0000000 --- a/scripts/git2irc/git2irc.conf +++ /dev/null @@ -1,12 +0,0 @@ -[git] -repositories=geany;geany-plugins;infrastructure;newsletter;talks;plugins.geany.org;www.geany.org;geany-themes - -[irc] -channel=#geany -host=localhost -port=7766 - -[shortener] -url=https://geany.org/s/api/create/ -username=username -password=123456-789-123456-789 diff --git a/scripts/git2irc/git2irc.py b/scripts/git2irc/git2irc.py deleted file mode 100755 index 172fdff..0000000 --- a/scripts/git2irc/git2irc.py +++ /dev/null @@ -1,247 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# git2irc.py - Notify the Geany IRC channel of Git commits -# -# Copyright 2012 Enrico Tröger -# Copyright 2012 Matthew Brush -# -# This program 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 program 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 program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, -# MA 02110-1301, USA. -# - -''' -Sends Git commit notifications to IRC via SweetGeany bot. - -Requires a file ``git2irc.conf`` which contains something like this:: - - [git] - repositories=one;or;more;repos - - [irc] - channel=#thechannel - host=hostname - port=portnum - - [shortener] - url=http://tiny.cc/ - login=apiuser - key=apikey - -Not having any of the sections, options or values will result in a run-time -error. No smart checking is performed. -''' - -from cgi import FieldStorage -from configparser import ConfigParser -from copy import deepcopy -from json import dumps, loads -from urllib.request import Request, urlopen -import logging -import logging.handlers -import socket - - -# hard-coded constants, adjust for environment -CONFIG_FILENAME = '/home/geany/git2irc.conf' -LOG_FILENAME = '/var/log/git2irc.log' -# extend on demand -LOG_EMAIL_ADDRESSES = ['enrico@geany.org'] - -# user-agent to be used for requests to tiny.cc -USER_AGENT = 'git2irc.py - https://raw.github.com/geany/infrastructure/master/scripts/git2irc/git2irc.py' - -# global and cuts across concerns, assumed to be properly initialized later -logger = None # see init_logging() -config = {'git': {}, 'irc': {}, 'shortener': {}} # see init_config() - - -# ---------------------------------------------------------------------- -def init_config(conf_filename): - """ - Reads the configuration file into a global dictionary. - """ - try: - conf = ConfigParser({ - 'git': {'repositories': ''}, - 'irc': {'channel': '', 'host': '', 'port': 0}, - 'shortener': {'url': '', 'login': '', 'key': ''}}) - conf.read(conf_filename) - config['git']['repositories'] = [ - itm.strip() - for itm - in conf.get('git', 'repositories').split(';') - if itm.strip()] - config['irc']['channel'] = conf.get('irc', 'channel') - config['irc']['host'] = conf.get('irc', 'host') - config['irc']['port'] = int(conf.get('irc', 'port')) - config['shortener']['url'] = conf.get('shortener', 'url') - config['shortener']['username'] = conf.get('shortener', 'username') - config['shortener']['password'] = conf.get('shortener', 'password') - - # copy config and replace password before logging - config_for_logging = deepcopy(config) - config_for_logging['shortener']['password'] = '*******' - logger.debug('Read configuration dict: {}'.format(str(config_for_logging))) - # catch-all: will be for invalid config file/section/option, unknown - # filename, etc - except Exception as e: - logger.warning( - "Exception reading config file '{}': {}".format(conf_filename, str(e)), - exc_info=True) - - -# ---------------------------------------------------------------------- -def init_logging(): - """" - Initializes the logging file for all to use. - """ - global logger # used everywhere - logger = logging.getLogger('git2irc') - logger.setLevel(logging.DEBUG) - file_handler = logging.FileHandler(LOG_FILENAME) - file_handler.setLevel(logging.DEBUG) - formatter = logging.Formatter('%(asctime)s %(name)s: %(levelname)s: %(message)s') - file_handler.setFormatter(formatter) - logger.addHandler(file_handler) - # mail - mail_handler = logging.handlers.SMTPHandler( - 'localhost', - 'git-noreply@geany.org', - LOG_EMAIL_ADDRESSES, - 'Error on git_post_commit') - mail_handler.setLevel(logging.WARNING) - logger.addHandler(mail_handler) - logger.debug('Logging initialized') - - -# ---------------------------------------------------------------------- -def shorten_url(long_url): - """ - Uses the geany.org/s/ API to shorten URL's for nice IRC messages. - """ - request_data = dumps({ - "auth": { - "username": config['shortener']['username'], - "password": config['shortener']['password'] - }, - "url": { - "fullUrl": long_url - } - }) - request_data = request_data.encode('utf-8') - request_url = config['shortener']['url'] - short_url = long_url # default is to return same URL (ie. in case of error) - request = Request(request_url, headers={"User-Agent": USER_AGENT}, data=request_data) - try: - resp_file = urlopen(request) - response = resp_file.read() - resp_dict = loads(response.decode('utf-8')) - if int(resp_dict['statusCode']) == 200: - short_url = resp_dict['url']['shortUrl'] - logger.debug('Shortened URL: {}'.format(short_url)) - else: - logger.warning( - 'Error shortening URL: {}: {}'.format( - resp_dict['statusCode'], - resp_dict['errorMessage'])) - except Exception as exc: # generally, urllib2.URLError - # read JSON response but just give up if there is no JSON in the response - # and log only the raw error - try: - response = exc.read() - reponse_data = loads(response) - logger.warning( - 'Error shortening URL: {}: {}'.format( - reponse_data['statusCode'], - reponse_data['errorMessage'])) - except Exception: - logger.warning('Exception shortening URL: {}'.format(str(exc)), exc_info=True) - - return short_url - - -# ---------------------------------------------------------------------- -def send_commit(message): - """ - Dumps the message to IRC via SweetGeany. - """ - irc_message = 'Freenode {} {}'.format(config['irc']['channel'], message) - irc_bot_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - irc_bot_socket.connect((config['irc']['host'], config['irc']['port'])) - irc_bot_socket.send(irc_message.encode('utf-8')) - irc_bot_socket.close() - logger.debug('Message sent to IRC: {}'.format(message)) - - -# ---------------------------------------------------------------------- -def handle_irc_message(repository, content): - """ - Processes the post-commit-hook from Github.com web hooks. - """ - try: - branch = content['ref'] - branch = branch.rsplit('/', 1)[1] - except (KeyError, IndexError) as rev_parse_e: - logger.warning('An error occurred at ref parsing: {}'.format(rev_parse_e), exc_info=True) - branch = 'unknown' - - for commit in content['commits']: - author = commit['author'].get('username', 'Unknown User') - commit_id = commit['id'] - message = commit['message'].splitlines()[0] - url = shorten_url(commit['url']) - irc_line = '[{}/{}] {} - {} ({})'.format(repository, branch, author, message, url) - send_commit(irc_line) - logger.info( - "Sent message to channel '{}' for '{}' ({})".format( - config['irc']['channel'], - author, - commit_id)) - - -# ---------------------------------------------------------------------- -def main(): - """ - Script entry-point, reads from github.com request and processes the - event. - """ - # parse query string - arguments = FieldStorage(keep_blank_values=True) - - json = arguments.getvalue('payload') - content = loads(json) - if 'commits' in content: - repo = content['repository']['name'] - if repo in config['git']['repositories']: - handle_irc_message(repo, content) - - -# ---------------------------------------------------------------------- -if __name__ == '__main__': - init_logging() - logger.debug('Script started') - init_config(CONFIG_FILENAME) - - try: - main() - except Exception as e: - logger.warning('An error occurred: {}'.format(e), exc_info=True) - - print('Content-type: text/html') - print() - - logger.debug('Script complete') - logging.shutdown() diff --git a/scripts/git_hooks/geany_commit_utils.py b/scripts/git_hooks/geany_commit_utils.py index b8d6ef2..ebea384 100644 --- a/scripts/git_hooks/geany_commit_utils.py +++ b/scripts/git_hooks/geany_commit_utils.py @@ -3,16 +3,15 @@ # Author: Enrico Tröger # License: GPLv2 # + ''' Utility functions for the Geany GIT hook/mirror scripts ''' - -from subprocess import Popen, PIPE import logging +from subprocess import PIPE, Popen -#---------------------------------------------------------------------- def setup_file_logging(name, logfile): logger = logging.getLogger(name) logger.setLevel(logging.DEBUG) @@ -25,32 +24,32 @@ def setup_file_logging(name, logfile): return logger -#---------------------------------------------------------------------- def run_command(repository_path, command, redirect_stdout=None, run_as=None, logger=None): if run_as: command = ('sudo', '-u', run_as) + command - process = Popen(command, cwd=repository_path, stdout=PIPE, stderr=PIPE) - stdout, stderr = process.communicate() - output = u'' - if stdout: - output = u'%s\nStdout:\n%s' % (output, stdout) - if redirect_stdout: - target_file = open(redirect_stdout, 'w') - target_file.write(stdout) - target_file.close() - if stderr: - output = u'%s\nStderr:\n%s' % (output, stderr) - if logger: - exit_code = process.returncode - logger.debug(u'Command "%s" exited with code %s: %s' % (' '.join(command), exit_code, output)) - - -#---------------------------------------------------------------------- + with Popen(command, cwd=repository_path, stdout=PIPE, stderr=PIPE) as process: + stdout, stderr = process.communicate() + output = '' + if stdout: + stdout = stdout.decode('utf-8') + output = f'{output}\nStdout:\n{stdout}' + if redirect_stdout: + with open(redirect_stdout, 'w', encoding='utf-8') as target_file: + target_file.write(stdout) + + if stderr: + stderr = stderr.decode('utf-8') + output = f'{output}\nStderr:\n{stderr}' + if logger: + exit_code = process.returncode + logger.debug('Command "%s" exited with code %s: %s', ' '.join(command), exit_code, output) + + def update_repository(repository, repository_path, logger, run_as=None): - logger.info(u'Updating repository %s' % repository) + logger.info(f'Updating repository {repository}') run_command(repository_path, ('git', 'remote', 'update'), run_as=run_as, logger=logger) run_command(repository_path, ('git', 'update-server-info'), run_as=run_as, logger=logger) run_command(repository_path, ('git', 'log', '--max-count=1', '--format="%cd"', '--date=local'), - redirect_stdout='%s/_geany/cgit_age' % repository_path, + redirect_stdout=f'{repository_path}/_geany/cgit_age', logger=logger) diff --git a/scripts/git_hooks/github_commit_mail.py b/scripts/git_hooks/github_commit_mail.py index 2b760d1..68bdae9 100644 --- a/scripts/git_hooks/github_commit_mail.py +++ b/scripts/git_hooks/github_commit_mail.py @@ -8,29 +8,29 @@ Github Post-Receive commit hook ''' - -from dateutil import parser as dateutil_parser +import logging +import sys +import urllib.request +from email import charset +from email.header import Header from email.mime.text import MIMEText -from email.Header import Header -from email.utils import formatdate, formataddr +from email.utils import formataddr, formatdate from json import loads from smtplib import SMTP from time import mktime -import logging -import sys -import urllib2 + +from dateutil import parser as dateutil_parser + # Python likes to encode MIME messages with base64, I prefer plain text (#issue12552) -from email import charset charset.add_charset('utf-8', charset.SHORTEST) - HTTP_REQUEST_TIMEOUT = 30 LOG_LEVEL = logging.DEBUG -EMAIL_SENDER = u'git-noreply@geany.org' -EMAIL_HOST = u'localhost' -EMAIL_SUBJECT_TEMPLATE = u'[%(user)s/%(repository)s] %(short_hash)s: %(short_commit_message)s' -EMAIL_BODY_TEMPLATE = u'''Branch: %(branch)s +EMAIL_SENDER = 'git-noreply@geany.org' +EMAIL_HOST = 'localhost' +EMAIL_SUBJECT_TEMPLATE = '[%(user)s/%(repository)s] %(short_hash)s: %(short_commit_message)s' +EMAIL_BODY_TEMPLATE = '''Branch: %(branch)s Author: %(author)s Committer: %(committer)s Date: %(commit_date_formatted)s @@ -50,7 +50,7 @@ -------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure). ''' -EMAIL_DIFF_TEMPLATE = u'''Modified: %(filename)s +EMAIL_DIFF_TEMPLATE = '''Modified: %(filename)s %(changes)s lines changed, %(additions)s insertions(+), %(deletions)s deletions(-) =================================================================== %(patch)s @@ -75,11 +75,8 @@ } -######################################################################## -class CommitMailGenerator(object): - """""" +class CommitMailGenerator: - #---------------------------------------------------------------------- def __init__(self, user, repository, branch, commits, logger): self._user = user self._repository = repository @@ -87,49 +84,40 @@ def __init__(self, user, repository, branch, commits, logger): self._commits = commits self._logger = logger - #---------------------------------------------------------------------- def generate_commit_mails(self): for commit in self._commits: self._try_to_generate_commit_mail(commit) - #---------------------------------------------------------------------- def _try_to_generate_commit_mail(self, commit): try: self._generate_commit_mail(commit) - except Exception, e: - self._logger.error('An error occurred while processing commit %s: %s' % - (commit, e), exc_info=True) + except Exception as exc: + self._logger.error('An error occurred while processing commit %s: %s', + commit, exc, exc_info=True) - #---------------------------------------------------------------------- def _generate_commit_mail(self, commit): full_commit_info = self._query_commit_info(commit) commit_info = self._adapt_commit_info(full_commit_info) self._send_mail(commit_info) - #---------------------------------------------------------------------- def _query_commit_info(self, commit): - url_parameters = dict(user=self._user, - repository=self._repository, - commit=commit) - url = u'https://api.github.com/repos/%(user)s/%(repository)s/commits/%(commit)s' % \ - url_parameters - handle = urllib2.urlopen(url, timeout=HTTP_REQUEST_TIMEOUT) - self._log_rate_limit(handle) - # parse response - response_json = handle.read() - response = loads(response_json) + url = f'https://api.github.com/repos/{self._user}/{self._repository}/commits/{commit}' + + with urllib.request.urlopen(url, timeout=HTTP_REQUEST_TIMEOUT) as handle: + self._log_rate_limit(handle) + # parse response + response_json = handle.read() + response = loads(response_json) return response - #---------------------------------------------------------------------- def _log_rate_limit(self, urllib_handle): headers = urllib_handle.info() rate_limit_remaining = headers.get('X-RateLimit-Remaining', '') rate_limit = headers.get('X-RateLimit-Limit', '') length = headers.get('Content-Length', '') - self._logger.debug(u'Github rate limits: %s/%s (%s bytes received)' % - (rate_limit_remaining, rate_limit, length)) + self._logger.debug('Github rate limits: %s/%s (%s bytes received)', + rate_limit_remaining, rate_limit, length) - #---------------------------------------------------------------------- def _adapt_commit_info(self, full_commit_info): branch = self._branch commit = full_commit_info['sha'] @@ -164,34 +152,27 @@ def _adapt_commit_info(self, full_commit_info): modified_files_list=modified_files_list, modified_files_diffs=modified_files_diffs) - #---------------------------------------------------------------------- def _generate_commit_url(self, commit): - url_parameters = dict(user=self._user, - repository=self._repository, - commit=commit) - return u'https://github.com/%(user)s/%(repository)s/commit/%(commit)s' % url_parameters + return f'https://github.com/{self._user}/{self._repository}/commit/{commit}' - #---------------------------------------------------------------------- def _get_name(self, full_commit_info, name): - return u'%s <%s>' % (full_commit_info['commit'][name]['name'], - full_commit_info['commit'][name]['email']) + commit_name = full_commit_info['commit'][name]['name'] + commit_email = full_commit_info['commit'][name]['email'] + return f'{commit_name} <{commit_email}>' - #---------------------------------------------------------------------- def _parse_commit_date(self, date_raw): return dateutil_parser.parse(date_raw) - #---------------------------------------------------------------------- def _get_short_commit_message(self, short_commit_message): return short_commit_message.splitlines()[0] - #---------------------------------------------------------------------- def _generate_modified_files_list(self, full_commit_info): modified_files = map(lambda x: x['filename'], full_commit_info['files']) - return u' %s' % u'\n '.join(modified_files) + files_list = '\n '.join(modified_files) + return f' {files_list}' - #---------------------------------------------------------------------- def _generate_modified_files_diffs(self, full_commit_info): - diffs = u'' + diffs = '' for modified_file in full_commit_info['files']: parameters = dict(filename=modified_file['filename'], changes=modified_file['changes'], @@ -202,22 +183,20 @@ def _generate_modified_files_diffs(self, full_commit_info): # shrink diffs to at most ~ 100KB if len(diffs) > 100000: diffs = diffs[:100000] - diffs += u'@@ Diff output truncated at 100000 characters. @@\n' + diffs += '@@ Diff output truncated at 100000 characters. @@\n' return diffs - #---------------------------------------------------------------------- def _get_diff_if_available(self, modified_file): try: return modified_file['patch'] except KeyError: - return u'No diff available, check online' + return 'No diff available, check online' - #---------------------------------------------------------------------- def _send_mail(self, commit_info): author_name = commit_info['author_name'].encode('utf-8') author_name = str(Header(author_name, 'UTF-8')) content = EMAIL_BODY_TEMPLATE % commit_info - msg = MIMEText(content, 'plain', 'utf-8') + msg = MIMEText(content.encode('utf-8'), 'plain', 'utf-8') msg['Subject'] = EMAIL_SUBJECT_TEMPLATE % commit_info msg['From'] = formataddr((author_name, EMAIL_SENDER)) @@ -225,25 +204,22 @@ def _send_mail(self, commit_info): msg['Date'] = formatdate(commit_info['commit_date']) smtp_conn = SMTP(EMAIL_HOST) - smtp_conn.sendmail(EMAIL_SENDER, msg['To'].split(','), msg.as_string()) + message = msg.as_string() + smtp_conn.sendmail(EMAIL_SENDER, msg['To'].split(','), message.encode('utf-8')) smtp_conn.quit() - #---------------------------------------------------------------------- def _get_email_recipient(self): - repository = u'%s/%s' % (self._user, self._repository) + repository = f'{self._user}/{self._repository}' # no error handling on purpose, this should bail out if repository is not in the map return EMAIL_RECIPIENT_MAP[repository] -######################################################################## class CommandLineArgumentError(Exception): - #---------------------------------------------------------------------- def __str__(self): - return 'Usage: %s ...' % sys.argv[0] + return f'Usage: {sys.argv[0]} ...' -#---------------------------------------------------------------------- def setup_logging(): logging.basicConfig() logger = logging.getLogger('github_commit_mail_hook') @@ -252,7 +228,6 @@ def setup_logging(): return logger -#---------------------------------------------------------------------- def parse_command_line_arguments(): if len(sys.argv) < 5: raise CommandLineArgumentError() @@ -265,17 +240,16 @@ def parse_command_line_arguments(): return user, repository, branch, commits -#---------------------------------------------------------------------- def main(): logger = setup_logging() try: user, repository, branch, commits = parse_command_line_arguments() gen = CommitMailGenerator(user, repository, branch, commits, logger) gen.generate_commit_mails() - except CommandLineArgumentError, e: - print >> sys.stderr, e - except Exception, e: - logger.warn(u'An error occurred: %s', unicode(e), exc_info=True) + except CommandLineArgumentError as exc: + print(exc, file=sys.stderr) + except Exception as exc: + logger.warning('An error occurred: %s', str(exc), exc_info=True) logging.shutdown() diff --git a/scripts/git_hooks/post_commit_hook.py b/scripts/git_hooks/post_commit_hook.py old mode 100644 new mode 100755 index 705b0bd..34eeae1 --- a/scripts/git_hooks/post_commit_hook.py +++ b/scripts/git_hooks/post_commit_hook.py @@ -12,18 +12,17 @@ - send a commit mail to the mailing list ''' - +import logging +import logging.handlers from cgi import FieldStorage -from geany_commit_utils import setup_file_logging, update_repository from json import loads from os import unlink from os.path import exists -import github_commit_mail -import logging -import logging.handlers +import github_commit_mail +from geany_commit_utils import setup_file_logging, update_repository -LOG_FILENAME = u'/var/log/git_mirror.log' +LOG_FILENAME = '/var/log/git_mirror.log' VALID_UPDATE_REPOSITORIES = ( 'geany', 'geany-plugins', @@ -34,29 +33,27 @@ 'geany-osx', 'talks', 'geany-themes') -REPOSITORY_BASE_PATH = u'/srv/www/git.geany.org/repos/%s.git' -UPDATE_LOCK_FILE = u'%s/_geany/.update_lock' -UPDATE_NOTIFY_FILE = u'%s/_geany/.update_required' +REPOSITORY_BASE_PATH = '/srv/www/git.geany.org/repos/%s.git' +UPDATE_LOCK_FILE = '%s/_geany/.update_lock' +UPDATE_NOTIFY_FILE = '%s/_geany/.update_required' # extend on demand LOG_EMAIL_ADDRESSES = ['enrico@geany.org'] -#---------------------------------------------------------------------- def setup_logging(): - logger = setup_file_logging('post_commit_hook', LOG_FILENAME) + logger_ = setup_file_logging('post_commit_hook', LOG_FILENAME) # mail mail_handler = logging.handlers.SMTPHandler( - u'localhost', - u'git-noreply@geany.org', + 'localhost', + 'git-noreply@geany.org', LOG_EMAIL_ADDRESSES, - u'Error on git_post_commit') + 'Error on git_post_commit') mail_handler.setLevel(logging.WARNING) - logger.addHandler(mail_handler) + logger_.addHandler(mail_handler) - return logger + return logger_ -#---------------------------------------------------------------------- def handle_repository_update(repository): repository_path = REPOSITORY_BASE_PATH % repository lock_file_path = UPDATE_LOCK_FILE % repository_path @@ -65,19 +62,16 @@ def handle_repository_update(repository): # if there is currently an update process running, simply mark the repository to be updated # again later, a cronjob will pick it update_notify_path = UPDATE_NOTIFY_FILE % repository_path - update_notify = open(update_notify_path, 'w') - update_notify.write('1') - update_notify.close() - logger.warn(u'Not updating repository %s because it is locked, leaving a notify', repository) + with open(update_notify_path, 'w', encoding='utf-8') as update_notify: + update_notify.write('1') + + logger.warning('Not updating repository %s because it is locked, leaving a notify', repository) else: - lock_file = open(lock_file_path, 'w') - update_repository(repository, repository_path, logger) - # remove lockfile - lock_file.close() - unlink(lock_file_path) + with open(lock_file_path, 'w', encoding='utf-8'): + update_repository(repository, repository_path, logger) + unlink(lock_file_path) -#---------------------------------------------------------------------- def process_commit_mails(content): user = content['repository']['owner']['name'] repository = content['repository']['name'] @@ -90,7 +84,6 @@ def process_commit_mails(content): generator.generate_commit_mails() -#---------------------------------------------------------------------- def main(): # parse query string arguments = FieldStorage(keep_blank_values=True) @@ -109,12 +102,12 @@ def main(): logger = setup_logging() try: main() -except Exception, e: - logger.warn(u'An error occurred: %s', unicode(e), exc_info=True) +except Exception as exc: + logger.warning('An error occurred: %s', str(exc), exc_info=True) -print 'Content-type: text/html' -print +print('Content-type: text/html') +print() logging.shutdown() diff --git a/scripts/git_hooks/update_repositories.py b/scripts/git_hooks/update_repositories.py index fa97b7b..010318a 100644 --- a/scripts/git_hooks/update_repositories.py +++ b/scripts/git_hooks/update_repositories.py @@ -12,31 +12,29 @@ post_commit_hook script to be out-of-date. ''' -from geany_commit_utils import setup_file_logging, update_repository +import logging from os import listdir, unlink from os.path import exists, join -import logging +from geany_commit_utils import setup_file_logging, update_repository -LOG_FILENAME = u'/var/log/git_mirror.log' -REPOSITORY_BASE_PATH = u'/srv/www/git.geany.org/repos/' -UPDATE_LOCK_FILE = u'%s/_geany/.update_lock' -UPDATE_NOTIFY_FILE = u'%s/_geany/.update_required' +LOG_FILENAME = '/var/log/git_mirror.log' +REPOSITORY_BASE_PATH = '/srv/www/git.geany.org/repos/' +UPDATE_LOCK_FILE = '%s/_geany/.update_lock' +UPDATE_NOTIFY_FILE = '%s/_geany/.update_required' -#---------------------------------------------------------------------- def setup_logging(): - logger = setup_file_logging('update_repositories', LOG_FILENAME) + logger_ = setup_file_logging('update_repositories', LOG_FILENAME) handler = logging.StreamHandler() handler.setLevel(logging.DEBUG) formatter = logging.Formatter('%(asctime)s %(name)s: %(levelname)s: %(message)s') handler.setFormatter(formatter) - logger.addHandler(handler) + logger_.addHandler(handler) - return logger + return logger_ -#---------------------------------------------------------------------- def handle_repository_update(repository): repository_path = join(REPOSITORY_BASE_PATH, repository) lock_file_path = UPDATE_LOCK_FILE % repository_path @@ -46,20 +44,16 @@ def handle_repository_update(repository): return if exists(update_notify_path): - update_notify_file = open(update_notify_path, 'r+') - need_update = update_notify_file.read() == '1' - if need_update: - lock_file = open(lock_file_path, 'w') - update_repository(repository, repository_path, logger, run_as='www-data') - # remove lockfile - lock_file.close() - unlink(lock_file_path) - # unmark update notify - update_notify_file.truncate(0) - update_notify_file.close() + with open(update_notify_path, 'r+', encoding='utf-8') as update_notify_file: + need_update = update_notify_file.read() == '1' + if need_update: + with open(lock_file_path, 'w', encoding='utf-8'): + update_repository(repository, repository_path, logger, run_as='www-data') + unlink(lock_file_path) + # unmark update notify + update_notify_file.truncate(0) -#---------------------------------------------------------------------- def main(): repositories = listdir(REPOSITORY_BASE_PATH) for repository in repositories: @@ -70,6 +64,6 @@ def main(): logger = setup_logging() try: main() - except Exception, e: - logger.warn(u'An error occurred: %s', unicode(e), exc_info=True) + except Exception as exc: + logger.warning('An error occurred: %s', str(exc), exc_info=True) logging.shutdown() diff --git a/scripts/irclog.py b/scripts/irclog.py deleted file mode 100644 index d0bd4f9..0000000 --- a/scripts/irclog.py +++ /dev/null @@ -1,395 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# IRC Log parser -# -# Copyright 2010 Enrico Tröger -# License: GPLv2 -# - -from os.path import exists -from html.entities import entitydefs -from time import ctime -import datetime -import re -import sys - - -CHANNEL = '#geany' -TITLE = 'IRC Logs for {} @ irc.freenode.net'.format(CHANNEL) - -MSG_TYPE_NORMAL = 1 -MSG_TYPE_STATUS = 2 -MSG_TYPE_ACTION = 3 - - -# 2010-01-31T14:09:29 logs and stats are currently broken(i.e. not up2date), ... -# 2010-01-31T14:09:34 I did not think that would still work -# 2010-01-31T14:09:45 I figured that -# 2010-01-31T14:09:52 I wrote a Geany plugin :) -REGEXP_MSG = re.compile(r'^(\d+)-([\d\w]+)-(\d+)[ T](\d+):(\d+):(\d+) <(\S+)> (.*)') - -# 2010-01-31T02:25:40 *** SweetGeany has joined #geany -# 2010-01-31T02:25:48 *** SweetGeany has left #geany -# 2010-01-31T02:26:57 *** SweetGeany has joined #geany -# 2010-01-31T03:25:54 *** _dmaphy_ has joined #geany -# 2010-01-31T03:27:36 *** dmaphy has quit IRC -# 2010-01-31T03:27:43 *** _dmaphy_ is now known as dmaphy -REGEXP_STATUS = re.compile(r'(\d+)-([\d\w]+)-(\d+)[ T](\d+):(\d+):(\d+) \*\*\* (\S+) ?(.*)') - -# 2010-01-31T14:17:18 * eht16 just noticed, 'make distcheck' is broken for Geany -REGEXP_ACTION = re.compile(r'(\d+)-([\d\w]+)-(\d+)[ T](\d+):(\d+):(\d+) \* (\S+) ?(.*)') - - -HTML_TEMPLATE = ''' - - - - {title} - - - - - - - - -

{title}

- - - - - - - - {content} -
TimeNickMessage
-

 

-

Stats generated by irclog.py - on {ctime} (times in UTC{date})

-

- - Valid XHTML 1.0 Strict - -     - - Geany - -

- - -''' - - -class LogEntry: - - # ---------------------------------------------------------------------- - def __init__(self): - self.date = None - self.nick = None - self.msg = None - self.msg_type = MSG_TYPE_NORMAL - - # ---------------------------------------------------------------------- - def __str__(self): - return 'LogEntry: {}: {}: {}'.format(self.date, self.nick, self.msg) - - -######################################################################## -class IrcLogFormatter: - - # ---------------------------------------------------------------------- - def __init__(self, input_filename, output_filename): - self._input_filename = input_filename - self._output_filename = output_filename - self._log_lines = None - self._last_nick_index = 1 - self._entitydefs_inverted = dict() - self._badchars_regex = None - self._been_fixed_regex = None - self._nick_color_index = dict() - self._setup_entity_definitions() - - # ---------------------------------------------------------------------- - def _setup_entity_definitions(self): - for key, value in entitydefs.items(): - value = value - self._entitydefs_inverted[value] = key - - self._badchars_regex = re.compile('|'.join(entitydefs.values())) - self._been_fixed_regex = re.compile(r'&\w+;|&#[0-9]+;') - - # ---------------------------------------------------------------------- - def format(self): - self._get_contents() - content = self._create_table() - if self._output_filename == '-': - date = '' - else: - date = ', will be generated every 24 hours' - - output = HTML_TEMPLATE.format( - title=TITLE, - content=content, - date=date, - ctime=ctime()) - - if self._output_filename == '-': - print(output) - else: - with open(self._output_filename, 'w') as output_file: - output_file.write(output) - - # ---------------------------------------------------------------------- - def _get_contents(self): - self._log_lines = list() - if not exists(self._input_filename): - # the logfile is missing after rotation and before Supybot has flushed the - # current log to disk, so use a dummy log message - entry = LogEntry() - entry.date = datetime.datetime.now() - entry.nick = 'SweetGeany' - entry.msg = 'Nothing has been logged yet' - self._log_lines.append(entry) - return - - with open(self._input_filename) as file_handle: - for line in file_handle: - match = REGEXP_MSG.match(line) - if match and len(match.groups()) == 8: - groups = match.groups() - entry = LogEntry() - entry.date = _create_datetime_from_tuple(groups) - entry.nick = groups[6] - entry.msg = groups[7].strip() - self._log_lines.append(entry) - else: - match = REGEXP_STATUS.match(line) - if match and len(match.groups()) == 8: - groups = match.groups() - entry = LogEntry() - entry.date = _create_datetime_from_tuple(groups) - entry.nick = groups[6] - entry.msg = groups[7] - entry.msg_type = MSG_TYPE_STATUS - self._log_lines.append(entry) - else: - match = REGEXP_ACTION.match(line) - if match and len(match.groups()) == 8: - groups = match.groups() - entry = LogEntry() - entry.date = _create_datetime_from_tuple(groups) - entry.nick = groups[6] - entry.msg = groups[7] - entry.msg_type = MSG_TYPE_ACTION - self._log_lines.append(entry) - else: - print('Failed: {}'.format(line), file=sys.stderr) - - # ---------------------------------------------------------------------- - def _create_table(self): - result = '' - row_index = 0 - last_day = 0 - - for entry in self._log_lines: - if not entry.msg: - continue - - # Date header - if last_day != entry.date.day: - date = entry.date.strftime('%Y-%m-%d') - result += '{}'.format(date) - last_day = entry.date.day - - row_index += 1 - if row_index % 2 == 0: - row_alt = ' class="dark"' - else: - row_alt = '' - - if entry.msg_type == MSG_TYPE_STATUS: - row_alt = ' class="status"' - entry.msg = '{} {}'.format(entry.nick, entry.msg) - entry.nick = '' - elif entry.msg_type == MSG_TYPE_ACTION: - row_alt = ' class="action"' - entry.msg = '{} {}'.format(entry.nick, entry.msg) - entry.nick = '' - - result += ''' - - {} - {} - {} - '''.format( - row_alt, - entry.date.strftime(u'%H:%M'), - self._get_color_index(entry.nick), - self._html_escape(entry.nick), - self._html_escape(entry.msg)) - - return result - - # ---------------------------------------------------------------------- - def _get_color_index(self, nick): - if nick not in self._nick_color_index: - if self._last_nick_index > 9: - self._last_nick_index = 1 - - self._nick_color_index[nick] = self._last_nick_index - self._last_nick_index += 1 - - return self._nick_color_index[nick] - - # ---------------------------------------------------------------------- - def _html_escape(self, text): - if self._been_fixed_regex.findall(text): - return text - - keyholder = dict() - for bad_char in self._badchars_regex.findall(text): - keyholder[bad_char] = 1 - - text = text.replace('&', '&') - text = text.replace('ß', 'ß') - text = text.replace('\x01', ' ') # ASCII char #1, SOH, sent by some clients for ACTIONs - text = text.replace('\x80', '€') - for each in keyholder: - if each == '&': - continue - - better = self._entitydefs_inverted[each] - if not better.startswith('&#'): - better = '&{};'.format(self._entitydefs_inverted[each]) - - text = text.replace(each, better) - return text - - -# ---------------------------------------------------------------------- -def _create_datetime_from_tuple(values): - return datetime.datetime( - int(values[0]), - int(values[1]), - int(values[2]), - int(values[3]), - int(values[4]), - int(values[5])) - - -# ---------------------------------------------------------------------- -def _try_to_decode(value): - try: - return value.encode('utf-8') - except UnicodeError: - try: - return value.encode('latin1') - except UnicodeError: - return repr(value) - - -# ---------------------------------------------------------------------- -def main(): - if len(sys.argv) < 3: - print('Usage: irclog.py ', file=sys.stderr) - exit(1) - - input_filename = sys.argv[1] - output_filename = sys.argv[2] - - irc_log_formatter = IrcLogFormatter(input_filename, output_filename) - irc_log_formatter.format() - - -if __name__ == '__main__': - main()