Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
sopel>=6.0,<7
sopel>=7.0,<8
bottle
sqlalchemy
16 changes: 8 additions & 8 deletions sopel_modules/github/formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,37 +38,37 @@
def fmt_url(s, row=None):
if not row:
row = current_row
return color(s, fg=row[3])
return color(s, fg=row.url_color)


def fmt_tag(s, row=None):
if not row:
row = current_row
return color(s, fg=row[4])
return color(s, fg=row.tag_color)


def fmt_repo(s, row=None):
if not row:
row = current_row
return color(s, fg=row[5])
return color(s, fg=row.repo_color)


def fmt_name(s, row=None):
if not row:
row = current_row
return color(s, fg=row[6])
return color(s, fg=row.name_color)


def fmt_hash(s, row=None):
if not row:
row = current_row
return color(s, fg=row[7])
return color(s, fg=row.hash_color)


def fmt_branch(s, row=None):
if not row:
row = current_row
return color(s, fg=row[8])
return color(s, fg=row.branch_color)


def fmt_short_comment_body(body):
Expand Down Expand Up @@ -270,14 +270,14 @@ def fmt_issue_title_edit(payload=None):
def fmt_issue_assignee_message(payload=None):
if not payload:
payload = current_payload

target = ''
self_assign = False
if (payload['assignee']['login'] == payload['sender']['login']):
self_assign = True
else:
target = 'to ' if payload['action'] == 'assigned' else 'from '
target = target + fmt_name(payload['assignee']['login'])
target = target + fmt_name(payload['assignee']['login'])
return '[{}] {} {}{} {} #{} {}'.format(
fmt_repo(payload['repository']['name']),
fmt_name(payload['sender']['login']),
Expand Down
126 changes: 83 additions & 43 deletions sopel_modules/github/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"""
github.py - Sopel GitHub Module
Copyright 2015 Max Gurela
Copyright 2019 dgw
Copyright 2019 dgw, Rusty Bower

_______ __ __ __ __
| __|__| |_| |--.--.--.| |--.
Expand All @@ -20,7 +20,11 @@

from . import formatting
from .formatting import shorten_url, emojize
from .webhook import setup_webhook, shutdown_webhook
from .utils import get_db_session
from .webhook import setup_webhook, shutdown_webhook, GithubHooks

from sqlalchemy.exc import OperationalError, SQLAlchemyError
from sqlalchemy.orm import scoped_session, sessionmaker

import operator
from collections import deque
Expand Down Expand Up @@ -118,6 +122,16 @@ def shutdown(sopel):
'''


def get_db_session(bot):
try:
engine = bot.db.connect()
except OperationalError:
print("OperationalError: Unable to connect to database.")
raise

return scoped_session(sessionmaker(bind=engine))
Comment on lines +125 to +132
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this pulled in from .utils now?



def fetch_api_endpoint(bot, url):
oauth = ''
if bot.config.github.client_id and bot.config.github.client_secret:
Expand Down Expand Up @@ -381,33 +395,45 @@ def configure_repo_messages(bot, trigger):
'state': '{}:{}'.format(repo_name, channel)}
auth_url = 'https://github.com/login/oauth/authorize?{}'.format(urlencode(auth_data))

conn = bot.db.connect()
c = conn.cursor()

c.execute('SELECT * FROM gh_hooks WHERE channel = ? AND repo_name = ?', (channel, repo_name))
result = c.fetchone()
if not result:
c.execute('''INSERT INTO gh_hooks (channel, repo_name, enabled) VALUES (?, ?, ?)''', (channel, repo_name, enabled))
bot.say("Successfully enabled listening for {repo}'s events in {chan}.".format(chan=channel, repo=repo_name))
bot.say('Great! Please allow me to create my webhook by authorizing via this link: ' + shorten_url(auth_url))
bot.say('Once that webhook is successfully created, I\'ll post a message in here. Give me about a minute or so to set it up after you authorize. You can configure the colors that I use to display webhooks with {}gh-hook-color'.format(bot.config.core.help_prefix))
else:
c.execute('''UPDATE gh_hooks SET enabled = ? WHERE channel = ? AND repo_name = ?''', (enabled, channel, repo_name))
bot.say("Successfully {state} the subscription to {repo}'s events".format(state='enabled' if enabled else 'disabled', repo=repo_name))
if enabled:
session = get_db_session(bot)

try:
hook = session.query(GithubHooks) \
.filter(GithubHooks.channel == channel) \
.filter(GithubHooks.repo_name == repo_name) \
.one_or_none()
if hook is None:
hook = GithubHooks(channel=channel, repo_name=repo_name, enabled=enabled)
session.add(hook)
session.commit()

bot.say("Successfully enabled listening for {repo}'s events in {chan}.".format(chan=channel, repo=repo_name))
bot.say('Great! Please allow me to create my webhook by authorizing via this link: ' + shorten_url(auth_url))
bot.say('Once that webhook is successfully created, I\'ll post a message in here. Give me about a minute or so to set it up after you authorize. You can configure the colors that I use to display webhooks with {}gh-hook-color'.format(bot.config.core.help_prefix))
conn.commit()
conn.close()
else:
hook.channel = channel
hook.repo_name = repo_name
hook.enabled = enabled
session.commit()

bot.say("Successfully {state} the subscription to {repo}'s events".format(state='enabled' if enabled else 'disabled', repo=repo_name))
if enabled:
bot.say('Great! Please allow me to create my webhook by authorizing via this link: ' + shorten_url(auth_url))
bot.say('Once that webhook is successfully created, I\'ll post a message in here. Give me about a minute or so to set it up after you authorize. You can configure the colors that I use to display webhooks with {}gh-hook-color'.format(bot.config.core.help_prefix))
except SQLAlchemyError:
session.rollback()
raise
finally:
session.close()


@commands('gh-hook-color')
@require_chanmsg('[GitHub] GitHub hooks can only be configured in a channel')
@example('.gh-hook-color maxpowa/Inumuta 13 15 6 6 14 2')
def configure_repo_colors(bot, trigger):
'''
"""
.gh-hook-color <repo> <repo color> <name color> <branch color> <tag color> <hash color> <url color> - Set custom colors for the webhook messages (Uses mIRC color indicies)
'''
"""
allowed = bot.privileges[trigger.sender].get(trigger.nick, 0) >= OP
if not allowed and not trigger.admin:
return bot.msg(trigger.sender, 'You must be a channel operator to use this command!')
Expand All @@ -426,26 +452,40 @@ def configure_repo_colors(bot, trigger):
if len(colors) != 6:
return bot.say('You must provide exactly 6 colors! See "{}help gh-hook-color" for more information.'.format(bot.config.core.help_prefix))

conn = bot.db.connect()
c = conn.cursor()

c.execute('SELECT * FROM gh_hooks WHERE channel = ? AND repo_name = ?', (channel, repo_name))
result = c.fetchone()
if not result:
return bot.say('Please use "{}gh-hook {} enable" before attempting to configure colors!'.format(bot.config.core.help_prefix, repo_name))
else:
combined = colors
combined.append(channel)
combined.append(repo_name)
c.execute('''UPDATE gh_hooks SET repo_color = ?, name_color = ?, branch_color = ?, tag_color = ?,
hash_color = ?, url_color = ? WHERE channel = ? AND repo_name = ?''', combined)
conn.commit()
c.execute('SELECT * FROM gh_hooks WHERE channel = ? AND repo_name = ?', (channel, repo_name))
row = c.fetchone()
bot.say("[{}] Example name: {} tag: {} commit: {} branch: {} url: {}".format(
formatting.fmt_repo(repo_name, row),
formatting.fmt_name(trigger.nick, row),
formatting.fmt_tag('tag', row),
formatting.fmt_hash('c0mm17', row),
formatting.fmt_branch('master', row),
formatting.fmt_url('http://git.io/', row)))
session = get_db_session(bot)

try:
hook = session.query(GithubHooks) \
.filter(GithubHooks.channel == channel) \
.filter(GithubHooks.repo_name == repo_name) \
.one_or_none()
if hook is None:
return bot.say('Please use "{}gh-hook {} enable" before attempting to configure colors!'.format(bot.config.core.help_prefix, repo_name))
else:
hook.channel = channel
hook.repo_name = repo_name
hook.repo_color = colors[0]
hook.name_color = colors[1]
hook.branch_color = colors[2]
hook.tag_color = colors[3]
hook.hash_color = colors[4]
hook.url_color = colors[5]
session.commit()

row = session.query(GithubHooks) \
.filter(GithubHooks.channel == channel) \
.filter(GithubHooks.repo_name == repo_name) \
.one_or_none()

bot.say("[{}] Example name: {} tag: {} commit: {} branch: {} url: {}".format(
formatting.fmt_repo(repo_name, row),
formatting.fmt_name(trigger.nick, row),
formatting.fmt_tag('tag', row),
formatting.fmt_hash('c0mm17', row),
formatting.fmt_branch('master', row),
formatting.fmt_url('https://git.io/', row)))
except SQLAlchemyError:
session.rollback()
raise
finally:
session.close()
28 changes: 28 additions & 0 deletions sopel_modules/github/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# coding=utf8
"""
webhook.py - Sopel GitHub Module
Copyright 2015 Max Gurela
Copyright 2019 dgw, Rusty Bower

_______ __ __ __ __
| __|__| |_| |--.--.--.| |--.
| | | | _| | | || _ |
|_______|__|____|__|__|_____||_____|
________ __ __ __
| | | |.-----.| |--.| |--.-----.-----.| |--.-----.
| | | || -__|| _ || | _ | _ || <|__ --|
|________||_____||_____||__|__|_____|_____||__|__|_____|

"""
from sqlalchemy.exc import OperationalError, SQLAlchemyError
from sqlalchemy.orm import scoped_session, sessionmaker


def get_db_session(bot):
try:
engine = bot.db.connect()
except OperationalError:
print("OperationalError: Unable to connect to database.")
raise

return scoped_session(sessionmaker(bind=engine))
91 changes: 60 additions & 31 deletions sopel_modules/github/webhook.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"""
webhook.py - Sopel GitHub Module
Copyright 2015 Max Gurela
Copyright 2019 dgw
Copyright 2019 dgw, Rusty Bower

_______ __ __ __ __
| __|__| |_| |--.--.--.| |--.
Expand All @@ -24,6 +24,12 @@
from .formatting import get_formatted_response
from .formatting import fmt_repo
from .formatting import fmt_name
from .utils import get_db_session

from sqlalchemy import Boolean, Column, Integer, String
from sqlalchemy.exc import OperationalError, SQLAlchemyError
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import scoped_session, sessionmaker

from threading import Thread
import bottle
Expand All @@ -33,6 +39,22 @@
# Because I'm a horrible person
sopel_instance = None

Base = declarative_base()


class GithubHooks(Base):
__tablename__ = 'gh_hooks'
channel = Column(String(255), primary_key=True)
repo_name = Column(String(255), primary_key=True)
enabled = Column(Boolean, default=True)
url_color = Column(Integer, default=2)
tag_color = Column(Integer, default=6)
repo_color = Column(Integer, default=13)
name_color = Column(Integer, default=15)
hash_color = Column(Integer, default=14)
branch_color = Column(Integer, default=6)


def setup_webhook(sopel):
global sopel_instance
sopel_instance = sopel
Expand All @@ -46,32 +68,14 @@ def setup_webhook(sopel):
sopel.memory['gh_webhook_server'] = base
sopel.memory['gh_webhook_thread'] = server

conn = sopel.db.connect()
c = conn.cursor()

# Catch any errors connecting to database
try:
c.execute('SELECT * FROM gh_hooks')
except Exception:
create_table(sopel, c)
conn.commit()
conn.close()


def create_table(bot, c):
primary_key = '(channel, repo_name)'

c.execute('''CREATE TABLE IF NOT EXISTS gh_hooks (
channel TEXT,
repo_name TEXT,
enabled BOOL DEFAULT 1,
url_color TINYINT DEFAULT 2,
tag_color TINYINT DEFAULT 6,
repo_color TINYINT DEFAULT 13,
name_color TINYINT DEFAULT 15,
hash_color TINYINT DEFAULT 14,
branch_color TINYINT DEFAULT 6,
PRIMARY KEY {0}
)'''.format(primary_key))
engine = sopel.db.connect()
except OperationalError:
print("OperationalError: Unable to connect to database.")
raise

Base.metadata.create_all(engine)


def shutdown_webhook(sopel):
Expand Down Expand Up @@ -102,10 +106,35 @@ def stop(self):


def get_targets(repo):
conn = sopel_instance.db.connect()
c = conn.cursor()
c.execute('SELECT * FROM gh_hooks WHERE repo_name = ? AND enabled = 1', (repo.lower(), ))
return c.fetchall()
session = get_db_session(sopel_instance)

try:
targets = session.query(GithubHooks) \
.filter(GithubHooks.repo_name == repo.lower()) \
.filter(GithubHooks.enabled == 1) \
.all()
return targets
except SQLAlchemyError:
session.rollback()
raise
finally:
session.close()


def process_payload(payload, targets):
if payload['event'] == 'ping':
for row in targets:
sopel_instance.msg(row.channel, '[{}] {}: {} (Your webhook is now enabled)'.format(
fmt_repo(payload['repository']['name'], row),
fmt_name(payload['sender']['login'], row),
payload['zen']))
return

for row in targets:
messages = get_formatted_response(payload, row)
# Write the formatted message(s) to the channel
for message in messages:
sopel_instance.msg(row.channel, message)


def process_payload(payload, targets):
Expand Down Expand Up @@ -144,7 +173,7 @@ def webhook():
payload_handler.start()

# send HTTP response ASAP, hopefully within GitHub's very short timeout
return '{"channels":' + json.dumps([target[0] for target in targets]) + '}'
return '{"channels":' + json.dumps([target.channel for target in targets]) + '}'


@bottle.get('/auth')
Expand Down