Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ircbot: add shorturl plugin #94

Merged
merged 7 commits into from
Feb 28, 2019
Merged
Show file tree
Hide file tree
Changes from 3 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
35 changes: 31 additions & 4 deletions ircbot/plugin/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
def register(bot):
threading.Thread(target=help_server, args=(bot,), daemon=True).start()
bot.listen(r'^help$', help, require_mention=True)
bot.listen(r'^macros$', help_macro, require_mention=True)
bot.listen(r'^macros?$', help_macro, require_mention=True)
bot.listen(r'^shorturls?$', help_shorturls, require_mention=True)
Copy link
Member

Choose a reason for hiding this comment

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

Do we plan to have any authentication before deleting shorturls?

Copy link
Member

Choose a reason for hiding this comment

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

If possible, I would try to limit deletion to ops or half-ops (do we even have half-ops?)

Copy link
Member

Choose a reason for hiding this comment

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

same with replacement and similar non-adding commands



def help(bot, msg):
Expand All @@ -23,6 +24,11 @@ def help_macro(bot, msg):
msg.respond('https://ircbot.ocf.berkeley.edu/macros')


def help_shorturls(bot, msg):
"""Provide a link to the list of shorturls."""
msg.respond('https://ircbot.ocf.berkeley.edu/shorturls')


def build_request_handler(bot):
jinja_env = jinja2.Environment(
loader=jinja2.PackageLoader('ircbot', ''),
Expand All @@ -39,6 +45,11 @@ def render_response(self, template, **context):
self.end_headers()
self.wfile.write(rendered)

def render_404(self):
self.send_response(404, 'File not found')
self.end_headers()
self.wfile.write(b'404 File not found')

def do_GET(self):
if self.path == '/':
plugins = collections.defaultdict(set)
Expand All @@ -54,10 +65,26 @@ def do_GET(self):
'plugin/templates/macros.html',
macros=bot.plugins['macros'].list(bot),
)
elif self.path.startswith('/shorturls'):
query_items = self.path.lstrip('/').split('/')

if len(query_items) < 2 or not query_items[1]:
self.render_response(
'plugin/templates/shorturls.html',
shorturls=bot.plugins['shorturls'].list(bot),
)
else:
candidate_target = bot.plugins['shorturls'].retrieve(bot, '/'.join(query_items[1:]))

if candidate_target:
self.send_response(302, 'Found')
self.send_header('Content-Length', 0)
self.send_header('Location', candidate_target)
self.end_headers()
else:
self.render_404()
else:
self.send_response(404, 'File not found')
self.end_headers()
self.wfile.write(b'404 File not found')
self.render_404()

return RequestHandler

Expand Down
130 changes: 130 additions & 0 deletions ircbot/plugin/shorturls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
"""Control ocfweb shorturls through ircbot."""
from ircbot import db

KEYWORDS = {'add', 'delete', 'rename', 'replace'}


def register(bot):
# [!-~] is all printable ascii except spaces
Copy link
Member

Choose a reason for hiding this comment

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

Do we want shorturls to be able to contain ?, &, or =?

Copy link
Member

@kpengboy kpengboy Jan 30, 2019

Choose a reason for hiding this comment

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

...Well now this won't allow hyphens or underscores. Methinks those are still useful characters.
(Also I think you need to update the comment)

bot.listen(r'^!shorturl ([\w/]+)', show)
bot.listen(r'^!shorturl add ([\w/]+) (.+)$', add)
bot.listen(r'^!shorturl delete ([\w/]+)$', delete)
bot.listen(r'^!shorturl rename ([\w/]+) ([\w/]+)$', rename)
bot.listen(r'^!shorturl replace ([\w/]+) (.+)$', replace)


def list(bot):
"""List all shorturls for shorturls help page."""

with db.cursor(password=bot.mysql_password) as c:
c.execute('SELECT slug, target FROM shorturls ORDER BY slug')

for entry in c.fetchall():
yield entry['slug'], entry['target']


def retrieve(bot, slug):
"""Reusable function to retrieve a shorturl by slug from the DB."""
Copy link
Member

Choose a reason for hiding this comment

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

Does this need the bot param?


with db.cursor(password=bot.mysql_password) as c:
c.execute(
'SELECT target FROM shorturls WHERE slug = %s',
(slug,),
)
target = c.fetchone()

return target['target'] if target else None


def show(bot, msg):
"""Return a shorturl by slug."""

slug = msg.match.group(1)

# special case these so show doesn't trigger on add/delete
# while still letting the trigger appear anywhere in the msg
if slug in KEYWORDS:
return

target = retrieve(bot, slug)
if not target:
msg.respond('shorturl `{}` does not exist.'.format(slug))
else:
msg.respond(target['target'], ping=False)


def add(bot, msg):
"""Add a new shorturl."""

slug = msg.match.group(1)
target = msg.match.group(2)

if slug in KEYWORDS:
msg.respond('`{}` is a reserved keyword.'.format(slug))
return

if len(slug) > 255:
msg.respond('shorturl slugs must be <= 255 characters')
Copy link
Member

Choose a reason for hiding this comment

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

this unfairly discriminates against all of those massive facebook URLs with a million query parameters! how else will Big ZUCC be able to do the machine learnings to see how people are clicking the links? /s

Copy link
Member Author

Choose a reason for hiding this comment

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

this only limits the size of the slug itself, not the target

return

with db.cursor(password=bot.mysql_password) as c:

c.execute('SELECT * FROM shorturls WHERE slug = %s', (slug,))
result = c.fetchone()
if result is not None:
msg.respond(
'shorturl `{}` already exists as {}'.format(
result['slug'],
result['target'],
),
)
else:
c.execute(
'INSERT INTO shorturls (slug, target) VALUES (%s, %s)',
(slug, target),
)
msg.respond('shorturl added as `{}`'.format(slug))


def delete(bot, msg):
"""Delete a shorturl."""

slug = msg.match.group(1)
with db.cursor(password=bot.mysql_password) as c:
c.execute(
'DELETE FROM shorturls WHERE slug = %s',
(slug,),
)
msg.respond('shorturl `{}` has been deleted.'.format(slug))


def rename(bot, msg):
"""Rename a shorturl."""

old_slug = msg.match.group(1)
new_slug = msg.match.group(2)

if new_slug in KEYWORDS:
msg.respond('`{}` is a reserved keyword.'.format(new_slug))
return

with db.cursor(password=bot.mysql_password) as c:
c.execute(
'UPDATE shorturls SET slug = %s WHERE slug = %s',
(new_slug, old_slug),
)
msg.respond('shorturl `{}` has been renamed to `{}`'.format(old_slug, new_slug))
Copy link
Member

Choose a reason for hiding this comment

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

what happens when the old slug does not exist?

Copy link
Member Author

Choose a reason for hiding this comment

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

nothing, the where clause doesn't match anything so it's a no-op



def replace(bot, msg):
"""Replace the target of a shorturl slug."""

slug = msg.match.group(1)
new_target = msg.match.group(2)

with db.cursor(password=bot.mysql_password) as c:
c.execute(
'UPDATE shorturls SET target = %s WHERE slug = %s',
(new_target, slug),
)
msg.respond('shorturl `{}` updated'.format(slug))
Copy link
Member

Choose a reason for hiding this comment

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

likewise, what happens when the slug does not exist?

Copy link
Member Author

Choose a reason for hiding this comment

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

ibid

26 changes: 26 additions & 0 deletions ircbot/plugin/templates/shorturls.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<!doctype html>
<html lang="en">
<head>
<title>ocf shorturls</title>
<style>
code { background-color: #eee; padding: 3px; }
.slug { font-weight: bold }
.link {}
</style>
</head>
<body>
<h1>shorturls available at ocf</h1>
<hr />

<p>Available shorturls are:</p>
<ul>
{% for slug, target in shorturls %}
<li>
<p><span class="slug">{{slug}}</span>: <span class="link"><a href="{{ target }}">{{ target }}</a></span></p>
</li>
{% endfor %}
</ul>
</body>
</html>
{# vim: ft=jinja
#}