degesch: add the first Lua plugin to distribution

This required separate plugin directories for both pluginized executables.
This commit is contained in:
2015-11-21 22:47:52 +01:00
parent d135728424
commit 71f3532e04
9 changed files with 41 additions and 6 deletions

128
plugins/zyklonb/coin Executable file
View File

@@ -0,0 +1,128 @@
#!/usr/bin/env tclsh
#
# ZyklonB coin plugin, random number-based utilities
#
# Copyright 2012, 2014 Přemysl Janouch
# See the file LICENSE for licensing information.
#
# This is a terrible excuse for a programming language and I feel dirty.
proc parse {line} {
global msg
unset -nocomplain msg
if [regexp {^:([^ ]*) *(.*)} $line -> prefix rest] {
set msg(prefix) $prefix
set line $rest
}
if [regexp {^([^ ]*) *(.*)} $line -> command rest] {
set msg(command) $command
set line $rest
}
while {1} {
set line [string trimleft $line " "]
set i [string first " " $line]
if {$i == -1} { set i [string length $line] }
if {$i == 0} { break }
if {[string index $line 0] == ":"} {
lappend msg(param) [string range $line 1 end]
break
}
lappend msg(param) [string range $line 0 [expr $i - 1]]
set line [string range $line $i end]
}
}
proc get_config {key} {
global msg
puts "ZYKLONB get_config :$key"
gets stdin line
parse $line
return [lindex $msg(param) 0]
}
proc pmrespond {text} {
global ctx
global ctx_quote
puts "PRIVMSG $ctx :$ctx_quote$text"
}
fconfigure stdin -translation crlf -encoding iso8859-1
fconfigure stdout -translation crlf -encoding iso8859-1
set prefix [get_config prefix]
puts "ZYKLONB register"
set eightball [list \
"It is certain" \
"It is decidedly so" \
"Without a doubt" \
"Yes - definitely" \
"You may rely on it" \
"As I see it, yes" \
"Most likely" \
"Outlook good" \
"Yes" \
"Signs point to yes" \
"Reply hazy, try again" \
"Ask again later" \
"Better not tell you now" \
"Cannot predict now" \
"Concentrate and ask again" \
"Don't count on it" \
"My reply is no" \
"My sources say no" \
"Outlook not so good" \
"Very doubtful"]
while {[gets stdin line] != -1} {
parse $line
if {! [info exists msg(prefix)] || ! [info exists msg(command)]
|| $msg(command) != "PRIVMSG" || ! [info exists msg(param)]
|| [llength $msg(param)] < 2} { continue }
regexp {^[^!]*} $msg(prefix) ctx
if [regexp {^[#&+!]} [lindex $msg(param) 0]] {
set ctx_quote "$ctx: "
set ctx [lindex $msg(param) 0]
} else { set ctx_quote "" }
set input [lindex $msg(param) 1]
set first_chars [string range $input 0 \
[expr [string length $prefix] - 1]]
if {$first_chars != $prefix} { continue }
set input [string range $input [string length $prefix] end]
if {$input == "coin"} {
if {rand() < 0.5} {
pmrespond "Heads."
} else {
pmrespond "Tails."
}
} elseif {[regexp {^dice( +|$)(.*)} $input -> _ args]} {
if {! [string is integer -strict $args] || $args <= 0} {
pmrespond "Invalid or missing number."
} else {
pmrespond [expr {int($args * rand()) + 1}]
}
} elseif {[regexp {^(choose|\?)( +|$)(.*)} $input -> _ _ args]} {
if {$args == ""} {
pmrespond "Nothing to choose from."
} else {
set c [split $args ",|"]
pmrespond [string trim [lindex $c \
[expr {int([llength $c] * rand())}]]]
}
} elseif {[regexp {^eightball( +|$)(.*)} $input -> _ args]} {
if {$args == ""} {
pmrespond "You should, you know, ask something."
} else {
pmrespond [lindex $eightball \
[expr {int([llength $eightball] * rand())}]].
}
}
}

312
plugins/zyklonb/eval Executable file
View File

@@ -0,0 +1,312 @@
#!/usr/bin/awk -f
#
# ZyklonB eval plugin, LISP-like expression evaluator
#
# Copyright 2013, 2014 Přemysl Janouch
# See the file LICENSE for licensing information.
#
BEGIN \
{
RS = "\r"
ORS = "\r\n"
IGNORECASE = 1
srand()
prefix = get_config("prefix")
print "ZYKLONB register"
fflush("")
# All functions have to be in this particular array
min_args["int"] = 1
min_args["+"] = 1
min_args["-"] = 1
min_args["*"] = 1
min_args["/"] = 1
min_args["%"] = 1
min_args["^"] = 1
min_args["**"] = 1
min_args["exp"] = 1
min_args["sin"] = 1
min_args["cos"] = 1
min_args["atan2"] = 2
min_args["log"] = 1
min_args["rand"] = 0
min_args["sqrt"] = 1
min_args["pi"] = 0
min_args["e"] = 0
min_args["min"] = 1
min_args["max"] = 1
# Whereas here their presence is only optional
max_args["int"] = 1
max_args["sin"] = 1
max_args["cos"] = 1
max_args["atan2"] = 2
max_args["log"] = 1
max_args["rand"] = 0
max_args["sqrt"] = 1
max_args["pi"] = 0
max_args["e"] = 0
}
{
parse($0)
}
msg_command == "PRIVMSG" \
{
# Context = either channel or user nickname
match(msg_prefix, /^[^!]+/)
ctx = substr(msg_prefix, RSTART, RLENGTH)
if (msg_param[0] ~ /^[#&!+]/)
{
ctx_quote = ctx ": "
ctx = msg_param[0]
}
else
ctx_quote = ""
if (substr(msg_param[1], 1, length(prefix)) == prefix)
{
keyword = "eval"
text = substr(msg_param[1], 1 + length(prefix))
if (match(text, "^" keyword "([^A-Za-z0-9].*|$)"))
process_request(substr(text, 1 + length(keyword)))
}
}
{
fflush("")
}
function pmrespond (text)
{
print "PRIVMSG " ctx " :" ctx_quote text
}
function process_request (input, res, x)
{
delete funs
delete accumulator
delete n_args
res = ""
fun_top = 0
funs[0] = ""
accumulator[0] = 0
n_args[0] = 0
if (match(input, "^[ \t]*"))
input = substr(input, RLENGTH + 1)
if (input == "")
res = "expression missing"
while (res == "" && input != "") {
if (match(input, "^-?[0-9]+\\.?[0-9]*")) {
x = substr(input, RSTART, RLENGTH)
input = substr(input, RLENGTH + 1)
match(input, "^ *")
input = substr(input, RLENGTH + 1)
res = process_argument(x)
} else if (match(input, "^[(]([^ ()]+)")) {
x = substr(input, RSTART + 1, RLENGTH - 1)
input = substr(input, RLENGTH + 1)
match(input, "^ *")
input = substr(input, RLENGTH + 1)
if (!(x in min_args)) {
res = "undefined function '" x "'"
} else {
fun_top++
funs[fun_top] = x
accumulator[fun_top] = 636363
n_args[fun_top] = 0
}
} else if (match(input, "^[)] *")) {
input = substr(input, RLENGTH + 1)
res = process_end()
} else
res = "invalid input at '" substr(input, 1, 10) "...'"
}
if (res == "") {
if (fun_top != 0)
res = "unclosed '" funs[fun_top] "'"
else if (n_args[0] != 1)
res = "internal error, expected one result" \
", got " n_args[0] " instead"
}
if (res == "")
pmrespond(accumulator[0])
else
pmrespond(res)
}
function process_argument (arg)
{
if (fun_top == 0) {
if (n_args[0]++ != 0)
return "too many results, I only expect one"
accumulator[0] = arg
return ""
}
fun = funs[fun_top]
if (fun in max_args && max_args[fun] <= n_args[fun_top])
return "too many operands for " fun
if (fun == "int") {
accumulator[fun_top] = int(arg)
} else if (fun == "+") {
if (n_args[fun_top] == 0)
accumulator[fun_top] = arg
else
accumulator[fun_top] += arg
} else if (fun == "-") {
if (n_args[fun_top] == 0)
accumulator[fun_top] = arg
else
accumulator[fun_top] -= arg
} else if (fun == "*") {
if (n_args[fun_top] == 0)
accumulator[fun_top] = arg
else
accumulator[fun_top] *= arg
} else if (fun == "/") {
if (n_args[fun_top] == 0)
accumulator[fun_top] = arg
else if (arg == 0)
return "division by zero"
else
accumulator[fun_top] /= arg
} else if (fun == "%") {
if (n_args[fun_top] == 0)
accumulator[fun_top] = arg
else if (arg == 0)
return "division by zero"
else
accumulator[fun_top] %= arg
} else if (fun == "^" || fun == "**" || fun == "exp") {
if (n_args[fun_top] == 0)
accumulator[fun_top] = arg
else
accumulator[fun_top] ^= arg
} else if (fun == "sin") {
accumulator[fun_top] = sin(arg)
} else if (fun == "cos") {
accumulator[fun_top] = cos(arg)
} else if (fun == "atan2") {
if (n_args[fun_top] == 0)
accumulator[fun_top] = arg
else
accumulator[fun_top] = atan2(accumulator[fun_top], arg)
} else if (fun == "log") {
accumulator[fun_top] = log(arg)
} else if (fun == "rand") {
# Just for completeness, execution never gets here
} else if (fun == "sqrt") {
accumulator[fun_top] = sqrt(arg)
} else if (fun == "min") {
if (n_args[fun_top] == 0)
accumulator[fun_top] = arg
else if (accumulator[fun_top] > arg)
accumulator[fun_top] = arg
} else if (fun == "max") {
if (n_args[fun_top] == 0)
accumulator[fun_top] = arg
else if (accumulator[fun_top] < arg)
accumulator[fun_top] = arg
} else
return "internal error, unhandled operands for " fun
n_args[fun_top]++
return ""
}
function process_end ()
{
if (fun_top <= 0)
return "extraneous ')'"
fun = funs[fun_top]
if (!(fun in min_args))
return "internal error, unhandled ')' for '" fun "'"
if (min_args[fun] > n_args[fun_top])
return "not enough operands for '" fun "'"
# There's no 'init' function to do it in
if (fun == "rand")
accumulator[fun_top] = rand()
else if (fun == "pi")
accumulator[fun_top] = 3.141592653589793
else if (fun == "e")
accumulator[fun_top] = 2.718281828459045
return process_argument(accumulator[fun_top--])
}
function get_config (key)
{
print "ZYKLONB get_config :" key
fflush("")
getline
parse($0)
return msg_param[0]
}
function parse (line, s, n, id, token)
{
s = 1
id = 0
# NAWK only uses the first character of RS
if (line ~ /^\n/)
line = substr(line, 2)
msg_prefix = ""
msg_command = ""
delete msg_param
n = match(substr(line, s), / |$/)
while (n)
{
token = substr(line, s, n - 1)
if (token ~ /^:/)
{
if (s == 1)
msg_prefix = substr(token, 2)
else
{
msg_param[id] = substr(line, s + 1)
break
}
}
else if (!msg_command)
msg_command = toupper(token)
else
msg_param[id++] = token
s = s + n
n = index(substr(line, s), " ")
if (!n)
{
n = length(substr(line, s)) + 1
if (n == 1)
break;
}
}
}

502
plugins/zyklonb/pomodoro Executable file
View File

@@ -0,0 +1,502 @@
#!/usr/bin/env ruby
# coding: utf-8
#
# ZyklonB pomodoro plugin
#
# Copyright 2015 Přemysl Janouch
# See the file LICENSE for licensing information.
#
# --- Simple event loop --------------------------------------------------------
# This is more or less a straight-forward port of my C event loop. It's a bit
# unfortunate that I really have to implement all this in order to get some
# basic asynchronicity but at least I get to exercise my Ruby.
class TimerEvent
attr_accessor :index, :when, :callback
def initialize (callback)
raise ArgumentError unless callback.is_a? Proc
@index = nil
@when = nil
@callback = callback
end
def active?
@index != nil
end
def until
return @when - Time.new
end
end
class IOEvent
READ = 1 << 0
WRITE = 1 << 1
attr_accessor :read_index, :write_index, :io, :callback
def initialize (io, callback)
raise ArgumentError unless callback.is_a? Proc
@read_index = nil
@write_index = nil
@io = io
@callback = callback
end
end
class EventLoop
def initialize
@running = false
@timers = []
@readers = []
@writers = []
@io_to_event = {}
end
def set_timer (timer, timeout)
raise ArgumentError unless timer.is_a? TimerEvent
timer.when = Time.now + timeout
if timer.index
heapify_down timer.index
heapify_up timer.index
else
timer.index = @timers.size
@timers.push timer
heapify_up timer.index
end
end
def reset_timer (timer)
raise ArgumentError unless timer.is_a? TimerEvent
remove_timer_at timer.index if timer.index
end
def set_io (io_event, events)
raise ArgumentError unless io_event.is_a? IOEvent
raise ArgumentError unless events.is_a? Numeric
reset_io io_event
@io_to_event[io_event.io] = io_event
if events & IOEvent::READ
io_event.read_index = @readers.size
@readers.push io_event.io
end
if events & IOEvent::WRITE
io_event.read_index = @writers.size
@writers.push io_event.io
end
end
def reset_io (io_event)
raise ArgumentError unless io_event.is_a? IOEvent
@readers.delete_at io_event.read_index if io_event.read_index
@writers.delete_at io_event.write_index if io_event.write_index
io_event.read_index = nil
io_event.write_index = nil
@io_to_event.delete io_event.io
end
def run
@running = true
while @running do one_iteration end
end
def quit
@running = false
end
private
def one_iteration
rs, ws, = IO.select @readers, @writers, [], nearest_timeout
dispatch_timers
(Array(rs) | Array(ws)).each do |io|
@io_to_event[io].callback.call io
end
end
def dispatch_timers
now = Time.new
while not @timers.empty? and @timers[0].when <= now do
@timers[0].callback.call
remove_timer_at 0
end
end
def nearest_timeout
return nil if @timers.empty?
timeout = @timers[0].until
if timeout < 0 then 0 else timeout end
end
def remove_timer_at (index)
@timers[index].index = nil
moved = @timers.pop
return if index == @timers.size
@timers[index] = moved
@timers[index].index = index
heapify_down index
end
def swap_timers (a, b)
@timers[a], @timers[b] = @timers[b], @timers[a]
@timers[a].index = a
@timers[b].index = b
end
def heapify_up (index)
while index != 0 do
parent = (index - 1) / 2
break if @timers[parent].when <= @timers[index].when
swap_timers index, parent
index = parent
end
end
def heapify_down (index)
loop do
parent = index
left = 2 * index + 1
right = 2 * index + 2
lowest = parent
lowest = left if left < @timers.size and
@timers[left] .when < @timers[lowest].when
lowest = right if right < @timers.size and
@timers[right].when < @timers[lowest].when
break if parent == lowest
swap_timers lowest, parent
index = lowest
end
end
end
# --- IRC protocol -------------------------------------------------------------
$stdin.set_encoding 'ASCII-8BIT'
$stdout.set_encoding 'ASCII-8BIT'
$stdin.sync = true
$stdout.sync = true
$/ = "\r\n"
$\ = "\r\n"
RE_MSG = /(?::([^! ]*)(?:!([^@]*)@([^ ]*))? +)?([^ ]+)(?: +(.*))?$/
RE_ARGS = /:?((?<=:).*|[^ ]+) */
def parse (line)
m = line.match RE_MSG
return nil if not m
nick, user, host, command, args = *m.captures
args = if args then args.scan(RE_ARGS).flatten else [] end
[nick, user, host, command, args]
end
def bot_print (what)
print "ZYKLONB print :#{what}"
end
# --- Initialization -----------------------------------------------------------
# We can only read in configuration from here so far
# To read it from anywhere else, it has to be done asynchronously
$config = {}
[:prefix].each do |name|
print "ZYKLONB get_config :#{name}"
_, _, _, _, args = *parse($stdin.gets.chomp)
$config[name] = args[0]
end
print "ZYKLONB register"
# --- Plugin logic -------------------------------------------------------------
# FIXME: this needs a major refactor as it doesn't make much sense at all
class MessageMeta < Struct.new(:nick, :user, :host, :channel, :ctx, :quote)
def respond (message)
print "PRIVMSG #{ctx} :#{quote}#{message}"
end
end
class Context
attr_accessor :nick, :ctx
def initialize (meta)
@nick = meta.nick
@ctx = meta.ctx
end
def == (other)
self.class == other.class \
and other.nick == @nick \
and other.ctx == @ctx
end
alias eql? ==
def hash
@nick.hash ^ @ctx.hash
end
end
class PomodoroTimer
def initialize (context)
@ctx = context.ctx
@nicks = [context.nick]
@timer_work = TimerEvent.new(lambda { on_work })
@timer_rest = TimerEvent.new(lambda { on_rest })
on_work
end
def inform (message)
# FIXME: it tells the nick even in PM's
quote = "#{@nicks.join(" ")}: "
print "PRIVMSG #{@ctx} :#{quote}#{message}"
end
def on_work
inform "work now!"
$loop.set_timer @timer_rest, 25 * 60
end
def on_rest
inform "rest now!"
$loop.set_timer @timer_work, 5 * 60
end
def join (meta)
return if @nicks.include? meta.nick
meta.respond "you have joined their pomodoro"
@nicks |= [meta.nick]
end
def part (meta, requested)
return if not @nicks.include? meta.nick
if requested
meta.respond "you have stopped your pomodoro"
end
@nicks -= [meta.nick]
if @nicks.empty?
$loop.reset_timer @timer_work
$loop.reset_timer @timer_rest
end
end
def status (meta)
return if not @nicks.include? meta.nick
if @timer_rest.active?
till = @timer_rest.until
meta.respond "working, #{(till / 60).to_i} minutes, " +
"#{(till % 60).to_i} seconds until rest"
end
if @timer_work.active?
till = @timer_work.until
meta.respond "resting, #{(till / 60).to_i} minutes, " +
"#{(till % 60).to_i} seconds until work"
end
end
end
class Pomodoro
KEYWORD = "pomodoro"
def initialize
@timers = {}
end
def on_help (meta, args)
meta.respond "usage: #{KEYWORD} { start | stop | join <nick> | status }"
end
def on_start (meta, args)
if args.size != 0
meta.respond "usage: #{KEYWORD} start"
return
end
context = Context.new meta
if @timers[context]
meta.respond "you already have a timer running here"
else
@timers[context] = PomodoroTimer.new meta
end
end
def on_join (meta, args)
if args.size != 1
meta.respond "usage: #{KEYWORD} join <nick>"
return
end
context = Context.new meta
if @timers[context]
meta.respond "you already have a timer running here"
return
end
joined_context = Context.new meta
joined_context.nick = args.shift
timer = @timers[joined_context]
if not timer
meta.respond "that person doesn't have a timer here"
else
timer.join meta
@timers[context] = timer
end
end
def on_stop (meta, args)
if args.size != 0
meta.respond "usage: #{KEYWORD} stop"
return
end
context = Context.new meta
timer = @timers[context]
if not timer
meta.respond "you don't have a timer running here"
else
timer.part meta, true
@timers.delete context
end
end
def on_status (meta, args)
if args.size != 0
meta.respond "usage: #{KEYWORD} status"
return
end
timer = @timers[Context.new meta]
if not timer
meta.respond "you don't have a timer running here"
else
timer.status meta
end
end
def process_command (meta, msg)
args = msg.split
return if args.shift != KEYWORD
method = "on_#{args.shift}"
send method, meta, args if respond_to? method
end
def on_server_nick (meta, command, args)
# TODO: either handle this properly...
happened = false
@timers.keys.each do |key|
next if key.nick != meta.nick
@timers[key].part meta, false
@timers.delete key
happened = true
end
if happened
# TODO: ...or at least inform the user via his new nick
end
end
def on_server_part (meta, command, args)
# TODO: instead of cancelling the user's pomodoros, either redirect
# them to PM's and later upon rejoining undo the redirection...
context = Context.new(meta)
context.ctx = meta.channel
if @timers.include? context
# TODO: ...or at least inform the user about the cancellation
@timers[context].part meta, false
@timers.delete context
end
end
def on_server_quit (meta, command, args)
@timers.keys.each do |key|
next if key.nick != meta.nick
@timers[key].part meta, false
@timers.delete key
end
end
def process (meta, command, args)
method = "on_server_#{command.downcase}"
send method, meta, command, args if respond_to? method
end
end
# --- IRC message processing ---------------------------------------------------
$handlers = [Pomodoro.new]
def process_line (line)
msg = parse line
return if not msg
nick, user, host, command, args = *msg
context = nick
quote = ""
channel = nil
if args.size >= 1 and args[0].start_with? ?#, ?+, ?&, ?!
case command
when "PRIVMSG", "NOTICE", "JOIN"
context = args[0]
quote = "#{nick}: "
channel = args[0]
when "PART"
channel = args[0]
end
end
# Handle any IRC message
meta = MessageMeta.new(nick, user, host, channel, context, quote).freeze
$handlers.each do |handler|
handler.process meta, command, args
end
# Handle pre-processed bot commands
if command == 'PRIVMSG' and args.size >= 2
msg = args[1]
return unless msg.start_with? $config[:prefix]
$handlers.each do |handler|
handler.process_command meta, msg[$config[:prefix].size..-1]
end
end
end
buffer = ""
stdin_io = IOEvent.new($stdin, lambda do |io|
begin
buffer << io.read_nonblock(4096)
lines = buffer.split $/, -1
buffer = lines.pop
lines.each { |line| process_line line }
rescue EOFError
$loop.quit
rescue IO::WaitReadable
# Ignore
end
end)
$loop = EventLoop.new
$loop.set_io stdin_io, IOEvent::READ
$loop.run

2310
plugins/zyklonb/script Executable file

File diff suppressed because it is too large Load Diff

111
plugins/zyklonb/youtube Executable file
View File

@@ -0,0 +1,111 @@
#!/usr/bin/env python3
#
# ZyklonB YouTube plugin, displaying info about YouTube links
#
# Copyright 2014 - 2015, Přemysl Janouch <p.janouch@gmail.com>
# See the file LICENSE for licensing information.
#
import sys
import io
import re
import json
import urllib.request
class Plugin:
re_msg = re.compile ('(?::([^! ]*)(?:!([^@]*)@([^ ]*))? +)?'
'([^ ]+)(?: +(.*))?\r\n$')
re_args = re.compile (':?((?<=:).*|[^ ]+) *')
def parse (self, line):
m = self.re_msg.match (line)
if m is None:
return None
(nick, user, host, command, args) = m.groups ()
args = [] if args is None else self.re_args.findall (args)
return (nick, user, host, command, args)
def get_config (self, key):
print ("ZYKLONB get_config :%s" % key)
(_, _, _, _, args) = self.parse (sys.stdin.readline ())
return args[0]
def bot_print (self, what):
print ('ZYKLONB print :%s' % what)
class YouTube (Plugin):
re_videos = [re.compile (x) for x in [
r'youtube\.[a-z]+/[^ ]*[&?]v=([-\w]+)',
r'youtube\.[a-z]+/v/([-\w]+)',
r'youtu\.be/([-\w]+)'
]]
re_playlists = [re.compile (x) for x in [
r'youtube\.[a-z]+/playlist[&?][^ ]*(?<=&|\?)list=([-\w]+)',
]]
def print_info (self, channel, url, cb):
try:
data = json.loads (urllib.request.urlopen
(url, None, 30).read ().decode ('utf-8'))
for line in map (lambda x: "YouTube: " + cb (x), data['items']):
print ("PRIVMSG %s :%s" % (channel,
line.encode ('utf-8').decode ('iso8859-1')))
except Exception as err:
self.bot_print ('youtube: %s' % (err))
def print_video_info (self, channel, video_id):
url = 'https://www.googleapis.com/youtube/v3/' \
+ 'videos?id=%s&key=%s&part=snippet,contentDetails,statistics' \
% (video_id, self.youtube_api_key)
self.print_info (channel, url, lambda x: "%s | %s | %sx" % (
x['snippet']['title'],
x['contentDetails']['duration'][2:].lower (),
x['statistics']['viewCount']))
def print_playlist_info (self, channel, playlist_id):
url = 'https://www.googleapis.com/youtube/v3/' \
+ 'playlists?id=%s&key=%s&part=snippet,contentDetails' \
% (playlist_id, self.youtube_api_key)
self.print_info (channel, url, lambda x: "%s | %d videos" % (
x['snippet']['title'],
x['contentDetails']['itemCount']))
def process_line (self, line):
msg = self.parse (line)
if msg is None:
return
(nick, user, host, command, args) = msg
if command != 'PRIVMSG' or len (args) < 2:
return
ctx = args[0]
if not ctx.startswith (('#', '+', '&', '!')):
ctx = nick
for regex in self.re_videos:
for i in regex.findall (args[1]):
self.print_video_info (ctx, i)
for regex in self.re_playlists:
for i in regex.findall (args[1]):
self.print_playlist_info (ctx, i)
def run (self):
self.youtube_api_key = self.get_config ('youtube_api_key')
if self.youtube_api_key == "":
self.bot_print ("youtube: missing `youtube_api_key'")
print ("ZYKLONB register")
for line in sys.stdin:
self.process_line (line)
sys.stdin = io.TextIOWrapper (sys.__stdin__.buffer,
encoding = 'iso8859-1', newline = '\r\n', line_buffering = True)
sys.stdout = io.TextIOWrapper (sys.__stdout__.buffer,
encoding = 'iso8859-1', newline = '\r\n', line_buffering = True)
YouTube ().run ()