Migrated memes to own file, and added reload functionallity.
This commit is contained in:
parent
e7dec7b5c4
commit
bf340a5daf
|
@ -1,6 +1,7 @@
|
|||
|
||||
local https = require 'ssl.https'
|
||||
local md5 = require 'md5'
|
||||
local json = require 'json'
|
||||
|
||||
local internet = {}
|
||||
|
||||
|
|
549
main.lua
549
main.lua
|
@ -28,11 +28,12 @@ local MEME_CHANCE = 0.3
|
|||
-- Make sure all required modules can be loaded
|
||||
|
||||
local imlib = require 'imlib2'
|
||||
for _, path in ipairs(CONFIG.IMGGEN_PATH_FONTS) do
|
||||
imlib.font.add_path(path)
|
||||
end
|
||||
|
||||
require 'socket'
|
||||
|
||||
local json = require 'json'
|
||||
|
||||
require 'irc.init'
|
||||
|
||||
local sleep = require 'socket'.sleep
|
||||
|
@ -42,411 +43,10 @@ local signal do
|
|||
if a then signal = b end
|
||||
end
|
||||
|
||||
local internet = require 'internet'
|
||||
local memes = require 'memes'
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Meme utils
|
||||
|
||||
for _, path in ipairs(CONFIG.IMGGEN_PATH_FONTS) do
|
||||
imlib.font.add_path(path)
|
||||
end
|
||||
|
||||
local function flatten_onto(target_img, other_img, x0, y0)
|
||||
assert(x0 + other_img:get_width() <= target_img:get_width())
|
||||
assert(y0 + other_img:get_height() <= target_img:get_height())
|
||||
|
||||
for x = 0, other_img:get_width() do
|
||||
for y = 0, other_img:get_height() do
|
||||
target_img:draw_pixel(x + x0, y + y0, other_img:get_pixel(x, y))
|
||||
end
|
||||
end
|
||||
--
|
||||
return
|
||||
end
|
||||
|
||||
local function clean_text (text)
|
||||
return text:gsub('%s+', ' '):match('^%s*(.-)%s*$')
|
||||
end
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Meme creations
|
||||
|
||||
local function generate_bait_link()
|
||||
return 'https://dcav.pw/jbait'
|
||||
end
|
||||
|
||||
local function fit_font_to_line_height (font_name, height)
|
||||
assert(type(font_name) == 'string')
|
||||
assert(type(height) == 'number')
|
||||
--
|
||||
local size_min, size_max = 1, 50
|
||||
while true do
|
||||
local size_guess = math.floor((size_max + size_min)/2)
|
||||
assert(size_min <= size_guess and size_guess <= size_max)
|
||||
local font = assert(imlib.font.load(font_name..'/'..size_guess))
|
||||
if size_guess == size_min then return font, size_guess end
|
||||
local text_w, text_h = font:get_size 'k'
|
||||
--
|
||||
if text_h <= height then size_min = size_guess
|
||||
else size_max = size_guess
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function load_font_of_size (font_name, size)
|
||||
return assert(imlib.font.load(font_name..'/'..size))
|
||||
end
|
||||
|
||||
local function determine_required_font_size (font_name, text, width)
|
||||
assert(type(font_name) == 'string')
|
||||
assert(type(text) == 'string')
|
||||
assert(type(width) == 'number')
|
||||
--
|
||||
local size_min, size_max = 1, 50
|
||||
while true do
|
||||
local size_guess = math.floor((size_max + size_min)/2)
|
||||
assert(size_min <= size_guess and size_guess <= size_max)
|
||||
local font = load_font_of_size(font_name, size_guess)
|
||||
if size_guess == size_min then return font, size_guess end
|
||||
local text_w, text_h = font:get_size(text)
|
||||
--
|
||||
if text_w <= width then size_min = size_guess
|
||||
else size_max = size_guess
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function wrap_lines (font, text, width)
|
||||
local lines, current, x = {}, {}, 0
|
||||
local SPACE_ADVANCE = font:get_advance ' '
|
||||
for word in text:gmatch '[^%s]+' do
|
||||
assert(type(word) == 'string')
|
||||
local word_w = font:get_advance(word)
|
||||
local next_x = x + word_w + SPACE_ADVANCE
|
||||
if word == '' then
|
||||
-- Do nothing
|
||||
elseif next_x <= width then
|
||||
current[#current+1], x = word, next_x
|
||||
else
|
||||
lines[#lines+1], current = table.concat(current, ' '), { word }
|
||||
x = word_w
|
||||
end
|
||||
end
|
||||
-- Færdiggør sidste linje
|
||||
lines[#lines+1] = table.concat(current, ' ')
|
||||
-- Ret
|
||||
return lines
|
||||
end
|
||||
|
||||
local COLOR_WHITE = imlib.color.new(255, 255, 255)
|
||||
local COLOR_BLACK = imlib.color.new( 0, 0, 0)
|
||||
|
||||
local function draw_centered_lines (draw_onto, lines, x0, y0, width, font, font_color)
|
||||
assert(type(lines) == 'table')
|
||||
local y = y0
|
||||
for i, line in ipairs(lines) do
|
||||
local text_w, text_h = font:get_size(line)
|
||||
local x = x0 + (width - text_w) / 2
|
||||
draw_onto:draw_text(font, line, x, y, font_color or COLOR_BLACK)
|
||||
--
|
||||
y = y + text_h
|
||||
end
|
||||
end
|
||||
|
||||
local function determine_font_and_lines_for_box (font_name, text, width, height)
|
||||
local num_lines, last_actual, actual_lines = 1, nil, nil
|
||||
for iteration = 1, 100 do
|
||||
local estimate_font = determine_required_font_size(font_name, text, width * num_lines)
|
||||
local line_height = select(2, estimate_font:get_size(text))
|
||||
|
||||
local estimate_num_lines = height / select(2, estimate_font:get_size(text))
|
||||
local lines = wrap_lines(estimate_font, text, width)
|
||||
local actual_num_lines = #lines
|
||||
if #lines * line_height >= height then
|
||||
estimate_num_lines = estimate_num_lines - 1
|
||||
end
|
||||
-- Test for convergence
|
||||
if last_actual == actual_num_lines then
|
||||
actual_lines = lines
|
||||
break
|
||||
end
|
||||
num_lines = (estimate_num_lines + actual_num_lines) / 2
|
||||
last_actual = actual_num_lines
|
||||
end
|
||||
|
||||
assert(actual_lines)
|
||||
-- Find final font size
|
||||
local _, min_font_size = fit_font_to_line_height(font_name, height / #actual_lines)
|
||||
for _, line in ipairs(actual_lines) do
|
||||
min_font_size = math.min(min_font_size, select(2, determine_required_font_size(font_name, line, width)))
|
||||
end
|
||||
local actual_font = load_font_of_size(font_name, min_font_size)
|
||||
|
||||
-- Assertions and error correction
|
||||
if (#actual_lines * select(2, actual_font:get_size(text)) < height) then
|
||||
io.write(' !! Performed bad fitting of text: '..text..'\n')
|
||||
actual_font, actual_lines = determine_required_font_size(font_name, text, width), {text}
|
||||
end
|
||||
|
||||
return actual_font, actual_lines
|
||||
end
|
||||
|
||||
local function draw_centered_text_in_box (font_name, draw_onto, text, x0, y0, width, height, bg_color, font_color)
|
||||
assert(type(font_name) == 'string')
|
||||
local font, lines = determine_font_and_lines_for_box(font_name, text, width, height)
|
||||
--
|
||||
local y = y0 + (height - #lines * select(2, font:get_size(text))) / 2
|
||||
--
|
||||
if bg_color then draw_onto:fill_rectangle(x0, y0, width, height, bg_color) end
|
||||
draw_centered_lines(draw_onto, lines, x0, y, width, font, font_color)
|
||||
end
|
||||
|
||||
local function choose_random_font ()
|
||||
local fonts = imlib.font.list_fonts()
|
||||
assert(#fonts > 0, 'Could not find any fonts')
|
||||
return fonts[math.random(#fonts)]
|
||||
end
|
||||
|
||||
local function load_random_font ()
|
||||
local font_name = choose_random_font()
|
||||
local font, errmsg = imlib.font.load(font_name..'/30')
|
||||
assert(font, errmsg)
|
||||
return font_name
|
||||
end
|
||||
|
||||
local DROSTE_EFFECT_TRIGGERS = {
|
||||
['induktion'] = true,
|
||||
['rekursion'] = true,
|
||||
['uendelig rekursion'] = true,
|
||||
['rekursive'] = true,
|
||||
['rekursive funktioner'] = true,
|
||||
['recursion'] = true,
|
||||
['infinite recursion'] = true,
|
||||
['tail recursion'] = true,
|
||||
['recursive'] = true,
|
||||
['recursive function'] = true,
|
||||
['induktion'] = true,
|
||||
['droste'] = true,
|
||||
['drostle'] = true, -- Misspelling
|
||||
|
||||
['uendelig'] = true,
|
||||
['uendeligt'] = true,
|
||||
|
||||
['strange loop'] = true,
|
||||
['loop'] = true,
|
||||
}
|
||||
|
||||
local DROSTE_ITERATIONS_DEFAULT = 5
|
||||
|
||||
local function fill_in_topics_information (topics)
|
||||
assert(type(topics) == 'table')
|
||||
--
|
||||
local topic_to_image_url = internet.search_images(topics)
|
||||
--
|
||||
local new_topics = {}
|
||||
for i, topic in ipairs(topics) do
|
||||
assert(type(topic) == 'string')
|
||||
|
||||
local url = topic_to_image_url[topic]
|
||||
|
||||
if DROSTE_EFFECT_TRIGGERS[topic] then new_topics[i] = { topic = topic, type = 'droste' }
|
||||
elseif url then new_topics[i] = { topic = topic, type = 'image', url = url }
|
||||
else new_topics[i] = { topic = topic, type = 'text', text = topic }
|
||||
end
|
||||
end
|
||||
return new_topics
|
||||
end
|
||||
|
||||
local function paste_topic_onto_image (target, topic, x, y, w, h, bg_color, font_name, font_color)
|
||||
assert(target)
|
||||
assert(type(topic) == 'table')
|
||||
assert(type(font_name) == 'string')
|
||||
-- Download and paste found image
|
||||
if topic.type == 'image' then
|
||||
local file_extension = topic.url:match '%.(%a+)$'
|
||||
local url, filename = topic.url, CONFIG.IMGGEN_PATH_OUTPUT..'topic_'..topic.topic..'.'..file_extension
|
||||
assert(type(url) == 'string' and #url > 0)
|
||||
assert(type(filename) == 'string' and #filename > 0)
|
||||
internet.download_file(url, filename)
|
||||
-- Convert svg to png
|
||||
if url:match '%.svg$' then
|
||||
local filename_2 = CONFIG.IMGGEN_PATH_OUTPUT..'topic_'..topic.topic..'.'..'png'
|
||||
os.execute('convert -density "1200" -resize 400x400 "'..filename..'" "'..filename_2..'"')
|
||||
filename = filename_2
|
||||
end
|
||||
--
|
||||
local found_img = assert(imlib.image.load(filename))
|
||||
found_img:crop_and_scale(0, 0, found_img:get_width(), found_img:get_height(), w, h)
|
||||
flatten_onto (target, found_img, x, y)
|
||||
found_img:free()
|
||||
--os.remove(filename)
|
||||
elseif topic.type == 'text' then
|
||||
local text = topic.text
|
||||
draw_centered_text_in_box(font_name, target, text, x, y, w, h, bg_color, font_color)
|
||||
elseif topic.type == 'droste' then
|
||||
target:fill_rectangle(x, y, w, h, COLOR_WHITE)
|
||||
else
|
||||
assert(false, topic.type)
|
||||
end
|
||||
end
|
||||
|
||||
local function copy_remotely (origin_server, origin_path, target_server, target_path)
|
||||
local origin = origin_server and origin_server ~= 'localhost' and origin_server..':'..origin_path or origin_path
|
||||
local target = target_server and target_server ~= 'localhost' and target_server..':'..target_path or target_path
|
||||
os.execute('scp '..origin..' '..target..' > /dev/null')
|
||||
end
|
||||
|
||||
local function save_to_cloud (img)
|
||||
assert(img)
|
||||
--
|
||||
local MEME_OUTPUT = CONFIG.IMGGEN_PATH_OUTPUT..'meme.png'
|
||||
img:save (MEME_OUTPUT)
|
||||
img:free()
|
||||
-- Upload to dcav
|
||||
local img_name = 'otmemes_'..os.time()..'.png'
|
||||
copy_remotely('localhost', MEME_OUTPUT, CONFIG.STORAGE_SERVER, CONFIG.STORAGE_SERVER_PATH..img_name)
|
||||
return CONFIG.STORAGE_DIR_URL..img_name
|
||||
end
|
||||
|
||||
|
||||
local function draw_droste_effect (target_image, droste_positions, iterations)
|
||||
iterations = interations or DROSTE_ITERATIONS_DEFAULT
|
||||
if #droste_positions <= 0 or iterations <= 0 then return target_image end
|
||||
for _ = 1, iterations do
|
||||
for _, position in ipairs(droste_positions) do
|
||||
local paste_image = target_image:clone()
|
||||
paste_image:crop_and_scale(0, 0, paste_image:get_width(), paste_image:get_height(), position.w, position.h)
|
||||
flatten_onto (target_image, paste_image, position.x, position.y)
|
||||
paste_image:free()
|
||||
end
|
||||
end
|
||||
return target_image
|
||||
end
|
||||
|
||||
local function droste_positions_from_topics (topics, places)
|
||||
local droste_poss = {}
|
||||
for i, topic in ipairs(topics) do
|
||||
if topic.type == 'droste' then
|
||||
droste_poss[#droste_poss+1] = {
|
||||
x = places[i].x, y = places[i].y,
|
||||
w = places[i].w, h = places[i].h,
|
||||
}
|
||||
end
|
||||
end
|
||||
return droste_poss
|
||||
end
|
||||
|
||||
local function shallow_copy (t0)
|
||||
local t = {}
|
||||
for k,v in pairs(t0) do t[k] = v end
|
||||
return t
|
||||
end
|
||||
|
||||
local function generate_comparison_meme_generator (positions)
|
||||
assert(type(positions) == 'table')
|
||||
assert(type(positions.base_img_path) == 'string')
|
||||
|
||||
return function (topics)
|
||||
assert(type(topics) == 'table' and #topics == #positions)
|
||||
|
||||
local font_name = choose_random_font()
|
||||
|
||||
local base_img = imlib.image.load(positions.base_img_path)
|
||||
|
||||
-- Randomize pos
|
||||
local rand_positions = {}
|
||||
for i, pos in ipairs(positions) do
|
||||
rand_positions[i] = shallow_copy(pos)
|
||||
rand_positions[i].x = pos.x + math.random(-(pos.xr or 0), pos.xr or 0)
|
||||
rand_positions[i].y = pos.y + math.random(-(pos.yr or 0), pos.yr or 0)
|
||||
end
|
||||
|
||||
-- Paste topic onto head
|
||||
for index, pos in ipairs(rand_positions) do
|
||||
paste_topic_onto_image(base_img, topics[index], pos.x, pos.y, pos.w, pos.h, nil, font_name, pos.font_color)
|
||||
end
|
||||
|
||||
-- Droste
|
||||
base_img = draw_droste_effect(base_img, droste_positions_from_topics(topics, rand_positions))
|
||||
|
||||
--
|
||||
return save_to_cloud(base_img)
|
||||
end
|
||||
end
|
||||
|
||||
local generate_distracted_boyfriend = generate_comparison_meme_generator {
|
||||
base_img_path = './images/distracted_boyfriend.jpg',
|
||||
|
||||
{ x = 640, xr = 20, y = 50, yr = 20, w = 150, h = 150 },
|
||||
{ x = 180, xr = 20, y = 50, yr = 20, w = 150, h = 150 },
|
||||
}
|
||||
|
||||
local generate_distracted_boyfriend_oldy_times = generate_comparison_meme_generator {
|
||||
base_img_path = './images/distracted_boyfriend_oldy_times.jpg',
|
||||
|
||||
{ x = 650, xr = 10, y = 100, yr = 20, w = 150, h = 150 },
|
||||
{ x = 124, xr = 10, y = 38, yr = 10, w = 250, h = 250 },
|
||||
}
|
||||
|
||||
local generate_drake_egon_olsen = generate_comparison_meme_generator {
|
||||
base_img_path = './images/drake_egon.png',
|
||||
|
||||
{ x = 380, xr = 0, y = 0, yr = 0, w = 380, h = 354 },
|
||||
{ x = 377, xr = 0, y = 354, yr = 0, w = 383, h = 360 },
|
||||
}
|
||||
|
||||
local generate_skorsten_image = generate_comparison_meme_generator {
|
||||
base_img_path = './images/bamse_skorsten_1.png',
|
||||
|
||||
{ x = 646, xr = 0, y = 366, yr = 0, w = 631, h = 215, font_color = COLOR_WHITE },
|
||||
}
|
||||
|
||||
local GENERATE_COMPARISON_MEME_OF_2 = {
|
||||
generate_distracted_boyfriend,
|
||||
generate_distracted_boyfriend_oldy_times,
|
||||
generate_drake_egon_olsen
|
||||
}
|
||||
|
||||
local BRAIN_ROW_HEIGHT = 150
|
||||
local BRAIN_ROW_WIDTH = 200
|
||||
|
||||
local BRAIN_MAX_EXPLOSION_TOPICS = 6
|
||||
local BRAIN_DROSTE_POS = {}
|
||||
for i = 1, BRAIN_MAX_EXPLOSION_TOPICS do
|
||||
BRAIN_DROSTE_POS[i] = {
|
||||
x = 0,
|
||||
y = (i-1) * BRAIN_ROW_HEIGHT,
|
||||
w = BRAIN_ROW_WIDTH,
|
||||
h = BRAIN_ROW_HEIGHT
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
local function generate_brain_explosion_image (topics)
|
||||
assert(type(topics) == 'table' and 1 <= #topics and #topics <= BRAIN_MAX_EXPLOSION_TOPICS)
|
||||
--
|
||||
local base_img = imlib.image.new(BRAIN_ROW_WIDTH * 2, BRAIN_ROW_HEIGHT * #topics)
|
||||
base_img:fill_rectangle(0, 0, BRAIN_ROW_WIDTH * 2, BRAIN_ROW_HEIGHT * #topics, COLOR_WHITE)
|
||||
local font_name = choose_random_font()
|
||||
--
|
||||
for i, topic_info in ipairs(topics) do
|
||||
paste_topic_onto_image(base_img, topic_info, 0, (i-1) * BRAIN_ROW_HEIGHT, BRAIN_ROW_WIDTH, BRAIN_ROW_HEIGHT, COLOR_WHITE, font_name)
|
||||
|
||||
local brain_img = imlib.image.load(CONFIG.IMGGEN_PATH_BRAINS..'brain_'..i..'.png')
|
||||
brain_img:crop_and_scale(0, 0, brain_img:get_width(), brain_img:get_height(), BRAIN_ROW_WIDTH, BRAIN_ROW_HEIGHT)
|
||||
flatten_onto (base_img, brain_img, BRAIN_ROW_WIDTH, (i-1) * BRAIN_ROW_HEIGHT)
|
||||
brain_img:free()
|
||||
end
|
||||
|
||||
-- Droste
|
||||
base_img = draw_droste_effect(base_img, droste_positions_from_topics(topics, BRAIN_DROSTE_POS))
|
||||
|
||||
-- Save
|
||||
return save_to_cloud(base_img)
|
||||
end
|
||||
-- Util
|
||||
|
||||
local function choose(l)
|
||||
assert(type(l) == 'table')
|
||||
|
@ -483,84 +83,19 @@ end
|
|||
local LAST_ACTIVE = os.time()
|
||||
local ACTIVE_PERIOD = 10
|
||||
|
||||
local function human_delay ()
|
||||
local function human_delay (start_meme_time)
|
||||
local delay = { 0.5, 0.5 }
|
||||
-- Not active
|
||||
if LAST_ACTIVE + ACTIVE_PERIOD < os.time() then
|
||||
delay = { 3, 3 }
|
||||
end
|
||||
-- Sleep
|
||||
sleep(math.random() * delay[2] + delay[1])
|
||||
local sleep_until = (start_meme_time or os.time()) + math.random() * delay[2] + delay[1]
|
||||
sleep(sleep_until - os.time())
|
||||
-- Set last active
|
||||
LAST_ACTIVE = os.time()
|
||||
end
|
||||
|
||||
local function is_comparison_message (message)
|
||||
local leq, gep = message:match '<', message:match '>'
|
||||
return leq and not gep and '<' or not leq and gep and '>'
|
||||
end
|
||||
|
||||
local function reverse (l0)
|
||||
local l, j = {}, #l0
|
||||
for i = 1, #l0 do
|
||||
l[i], j = l0[j], j - 1
|
||||
end
|
||||
return l
|
||||
end
|
||||
|
||||
local COMP_SENTENCES do
|
||||
local ORD_MED_ATTITYDE = {
|
||||
bedre = 'positiv',
|
||||
dårligere = 'negativ',
|
||||
hurtigere = 'positiv',
|
||||
langsommere = 'negativt',
|
||||
robust = 'positivt',
|
||||
}
|
||||
COMP_SENTENCES = {}
|
||||
for ord, attityde in pairs(ORD_MED_ATTITYDE) do
|
||||
local first, second = '{A}', '{B}'
|
||||
if attityde == 'negativ' then first, second = second, first end
|
||||
for _, fmt in ipairs { '%s er %s end %s', '%s er mere %s end %s', '%s er meget %s end %s', '%s %s end %s' } do
|
||||
table.insert(COMP_SENTENCES, fmt:format(first, ord, second))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for sentence_i, sentence in ipairs(COMP_SENTENCES) do
|
||||
local A_pos, B_pos = sentence:find '{A}', sentence:find '{B}'
|
||||
local best_first = (A_pos < B_pos)
|
||||
local pattern_sentence = sentence:gsub('{[AB]}', '(.+)'):gsub('%s+','%%s+')
|
||||
COMP_SENTENCES[pattern_sentence] = { best_first = best_first }
|
||||
COMP_SENTENCES[sentence_i] = nil
|
||||
end
|
||||
|
||||
local function get_topics_from_comparison_message(message)
|
||||
local comp = is_comparison_message(message)
|
||||
if comp then
|
||||
local topics = {}
|
||||
for topic in message:gmatch('[^'..comp..']+') do
|
||||
local topic_text = clean_text(topic)
|
||||
if topic_text:match '[^%s]' then
|
||||
topics[#topics+1] = topic_text
|
||||
end
|
||||
end
|
||||
-- Rev
|
||||
if comp == '>' then topics = reverse(topics) end
|
||||
--
|
||||
return (#topics >= 2) and topics or nil
|
||||
end
|
||||
|
||||
-- Natural language comparisons
|
||||
for pattern, sentence_info in pairs(COMP_SENTENCES) do
|
||||
local good, bad = message:lower():match(pattern)
|
||||
if not sentence_info.best_first then good, bad = bad, good end
|
||||
if good then return { bad, good } end
|
||||
end
|
||||
|
||||
--
|
||||
return nil
|
||||
end
|
||||
|
||||
local function common_error(channel, text, ...)
|
||||
local errmsg = text:format(...)
|
||||
bot:sendChat(channel, errmsg)
|
||||
|
@ -695,55 +230,15 @@ local function handle_message(bot, user, channel, message, is_fast_channel)
|
|||
return 'LUCK'
|
||||
end
|
||||
|
||||
-- Bait msg
|
||||
if message:match '%f[%a]bait%f[%A]' then
|
||||
human_delay()
|
||||
bot:sendChat(channel, generate_bait_link())
|
||||
local start_meme_time = os.time()
|
||||
|
||||
local reply, status = memes.generate_for_message(user, message)
|
||||
if reply then
|
||||
human_delay(start_meme_time)
|
||||
bot:sendChat(channel, reply)
|
||||
FORRIGE_MEME = os.time()
|
||||
return 'BAIT'
|
||||
return status
|
||||
end
|
||||
|
||||
-- Meme machine
|
||||
if message:match '%f[%a]meme%s+machine%f[%A]' then
|
||||
human_delay()
|
||||
bot:sendChat(channel, 'https://www.youtube.com/watch?v=wl-LeTFM8zo')
|
||||
return 'MACHINE'
|
||||
end
|
||||
|
||||
-- Vælter min skorsten
|
||||
do
|
||||
local problem_text = message:lower():match 'vælter%s+min%s+skorsten%s*(.+)$'
|
||||
if problem_text then
|
||||
if problem_text:match '^er' then problem_text = problem_text:match '^er%s*(.+)$' end
|
||||
if problem_text:match '^:' then problem_text = problem_text:match '^:%s*(.+)$' end
|
||||
local img_link = generate_skorsten_image {{ type = 'text', text = problem_text }}
|
||||
bot:sendChat(channel, img_link)
|
||||
return 'SKRSTEN'
|
||||
end
|
||||
end
|
||||
|
||||
-- Comparison memes
|
||||
local topics = get_topics_from_comparison_message(message)
|
||||
if not topics then return end
|
||||
|
||||
local topics = fill_in_topics_information(topics)
|
||||
|
||||
local img_link, error_message
|
||||
--
|
||||
if #topics == 2 then
|
||||
img_link, error_message = choose(GENERATE_COMPARISON_MEME_OF_2)(topics)
|
||||
else
|
||||
assert(#topics >= 3)
|
||||
img_link, error_message = generate_brain_explosion_image(topics)
|
||||
end
|
||||
--
|
||||
if img_link then
|
||||
bot:sendChat(channel, img_link)
|
||||
else
|
||||
bot:sendChat(channel, 'Sorry, no can do. '..tostring(error_message)..'.')
|
||||
end
|
||||
FORRIGE_MEME = os.time()
|
||||
return 'COMPAR'
|
||||
end
|
||||
|
||||
local DEBUG_CHANNEL = '#bot-test'
|
||||
|
@ -832,10 +327,22 @@ local function init_memebot ()
|
|||
if signal then
|
||||
signal.signal(signal.SIGINT, function (signum)
|
||||
io.write ' !! Received SIGINT, will shut down...\n'
|
||||
--send_to_all(BOT_FAREWELL[math.random(#BOT_FAREWELL)])
|
||||
send_to_all(BOT_FAREWELL[math.random(#BOT_FAREWELL)])
|
||||
shutdown_memebot()
|
||||
os.exit(128 + signum)
|
||||
end)
|
||||
signal.signal(signal.SIGUSR1, function (signum)
|
||||
io.write ' !! Received SIGUSR1, will reload modules...\n'
|
||||
for _, module_name in ipairs { 'internet', 'memes' } do
|
||||
local old_module = require(module_name)
|
||||
package.loaded[module_name] = nil
|
||||
local new_module = require(module_name)
|
||||
for k, v in pairs(new_module) do
|
||||
old_module[k] = v
|
||||
end
|
||||
io.write(' - '..module_name..'\n')
|
||||
end
|
||||
end)
|
||||
else
|
||||
io.write ' !! Module "posix.signal" was missing, so CTRL+C will hard-crash.\n'
|
||||
end
|
||||
|
|
537
memes.lua
Normal file
537
memes.lua
Normal file
|
@ -0,0 +1,537 @@
|
|||
|
||||
local memes = {}
|
||||
|
||||
local internet = require 'internet'
|
||||
local imlib = require 'imlib2'
|
||||
local CONFIG = require 'config'
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Util
|
||||
|
||||
local function reverse (l0)
|
||||
local l, j = {}, #l0
|
||||
for i = 1, #l0 do
|
||||
l[i], j = l0[j], j - 1
|
||||
end
|
||||
return l
|
||||
end
|
||||
|
||||
local function choose(l)
|
||||
assert(type(l) == 'table')
|
||||
return l[math.random(#l)]
|
||||
end
|
||||
|
||||
local function shallow_copy (t0)
|
||||
local t = {}
|
||||
for k,v in pairs(t0) do t[k] = v end
|
||||
return t
|
||||
end
|
||||
|
||||
local function clean_text (text)
|
||||
return text:gsub('%s+', ' '):match('^%s*(.-)%s*$')
|
||||
end
|
||||
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Internet shit
|
||||
|
||||
local function copy_remotely (origin_server, origin_path, target_server, target_path)
|
||||
local origin = origin_server and origin_server ~= 'localhost' and origin_server..':'..origin_path or origin_path
|
||||
local target = target_server and target_server ~= 'localhost' and target_server..':'..target_path or target_path
|
||||
os.execute('scp '..origin..' '..target..' > /dev/null')
|
||||
end
|
||||
|
||||
local function save_to_cloud (img)
|
||||
assert(img)
|
||||
--
|
||||
local MEME_OUTPUT = CONFIG.IMGGEN_PATH_OUTPUT..'meme.png'
|
||||
img:save (MEME_OUTPUT)
|
||||
img:free()
|
||||
-- Upload to dcav
|
||||
local img_name = 'otmemes_'..os.time()..'.png'
|
||||
copy_remotely('localhost', MEME_OUTPUT, CONFIG.STORAGE_SERVER, CONFIG.STORAGE_SERVER_PATH..img_name)
|
||||
return CONFIG.STORAGE_DIR_URL..img_name
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Draw shit
|
||||
|
||||
local function flatten_onto(target_img, other_img, x0, y0)
|
||||
assert(x0 + other_img:get_width() <= target_img:get_width())
|
||||
assert(y0 + other_img:get_height() <= target_img:get_height())
|
||||
|
||||
for x = 0, other_img:get_width() do
|
||||
for y = 0, other_img:get_height() do
|
||||
target_img:draw_pixel(x + x0, y + y0, other_img:get_pixel(x, y))
|
||||
end
|
||||
end
|
||||
--
|
||||
return
|
||||
end
|
||||
|
||||
local function fit_font_to_line_height (font_name, height)
|
||||
assert(type(font_name) == 'string')
|
||||
assert(type(height) == 'number')
|
||||
--
|
||||
local size_min, size_max = 1, 50
|
||||
while true do
|
||||
local size_guess = math.floor((size_max + size_min)/2)
|
||||
assert(size_min <= size_guess and size_guess <= size_max)
|
||||
local font = assert(imlib.font.load(font_name..'/'..size_guess))
|
||||
if size_guess == size_min then return font, size_guess end
|
||||
local text_w, text_h = font:get_size 'k'
|
||||
--
|
||||
if text_h <= height then size_min = size_guess
|
||||
else size_max = size_guess
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function load_font_of_size (font_name, size)
|
||||
return assert(imlib.font.load(font_name..'/'..size))
|
||||
end
|
||||
|
||||
local function determine_required_font_size (font_name, text, width)
|
||||
assert(type(font_name) == 'string')
|
||||
assert(type(text) == 'string')
|
||||
assert(type(width) == 'number')
|
||||
--
|
||||
local size_min, size_max = 1, 50
|
||||
while true do
|
||||
local size_guess = math.floor((size_max + size_min)/2)
|
||||
assert(size_min <= size_guess and size_guess <= size_max)
|
||||
local font = load_font_of_size(font_name, size_guess)
|
||||
if size_guess == size_min then return font, size_guess end
|
||||
local text_w, text_h = font:get_size(text)
|
||||
--
|
||||
if text_w <= width then size_min = size_guess
|
||||
else size_max = size_guess
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function wrap_lines (font, text, width)
|
||||
local lines, current, x = {}, {}, 0
|
||||
local SPACE_ADVANCE = font:get_advance ' '
|
||||
for word in text:gmatch '[^%s]+' do
|
||||
assert(type(word) == 'string')
|
||||
local word_w = font:get_advance(word)
|
||||
local next_x = x + word_w + SPACE_ADVANCE
|
||||
if word == '' then
|
||||
-- Do nothing
|
||||
elseif next_x <= width then
|
||||
current[#current+1], x = word, next_x
|
||||
else
|
||||
lines[#lines+1], current = table.concat(current, ' '), { word }
|
||||
x = word_w
|
||||
end
|
||||
end
|
||||
-- Færdiggør sidste linje
|
||||
lines[#lines+1] = table.concat(current, ' ')
|
||||
-- Ret
|
||||
return lines
|
||||
end
|
||||
|
||||
local COLOR_WHITE = imlib.color.new(255, 255, 255)
|
||||
local COLOR_BLACK = imlib.color.new( 0, 0, 0)
|
||||
|
||||
local function draw_centered_lines (draw_onto, lines, x0, y0, width, font, font_color)
|
||||
assert(type(lines) == 'table')
|
||||
local y = y0
|
||||
for i, line in ipairs(lines) do
|
||||
local text_w, text_h = font:get_size(line)
|
||||
local x = x0 + (width - text_w) / 2
|
||||
draw_onto:draw_text(font, line, x, y, font_color or COLOR_BLACK)
|
||||
--
|
||||
y = y + text_h
|
||||
end
|
||||
end
|
||||
|
||||
local function determine_font_and_lines_for_box (font_name, text, width, height)
|
||||
local num_lines, last_actual, actual_lines = 1, nil, nil
|
||||
for iteration = 1, 100 do
|
||||
local estimate_font = determine_required_font_size(font_name, text, width * num_lines)
|
||||
local line_height = select(2, estimate_font:get_size(text))
|
||||
|
||||
local estimate_num_lines = height / select(2, estimate_font:get_size(text))
|
||||
local lines = wrap_lines(estimate_font, text, width)
|
||||
local actual_num_lines = #lines
|
||||
if #lines * line_height >= height then
|
||||
estimate_num_lines = estimate_num_lines - 1
|
||||
end
|
||||
-- Test for convergence
|
||||
if last_actual == actual_num_lines then
|
||||
actual_lines = lines
|
||||
break
|
||||
end
|
||||
num_lines = (estimate_num_lines + actual_num_lines) / 2
|
||||
last_actual = actual_num_lines
|
||||
end
|
||||
|
||||
assert(actual_lines)
|
||||
-- Find final font size
|
||||
local _, min_font_size = fit_font_to_line_height(font_name, height / #actual_lines)
|
||||
for _, line in ipairs(actual_lines) do
|
||||
min_font_size = math.min(min_font_size, select(2, determine_required_font_size(font_name, line, width)))
|
||||
end
|
||||
local actual_font = load_font_of_size(font_name, min_font_size)
|
||||
|
||||
-- Assertions and error correction
|
||||
if (#actual_lines * select(2, actual_font:get_size(text)) < height) then
|
||||
io.write(' !! Performed bad fitting of text: '..text..'\n')
|
||||
actual_font, actual_lines = determine_required_font_size(font_name, text, width), {text}
|
||||
end
|
||||
|
||||
return actual_font, actual_lines
|
||||
end
|
||||
|
||||
local function draw_centered_text_in_box (font_name, draw_onto, text, x0, y0, width, height, bg_color, font_color)
|
||||
assert(type(font_name) == 'string')
|
||||
local font, lines = determine_font_and_lines_for_box(font_name, text, width, height)
|
||||
--
|
||||
local y = y0 + (height - #lines * select(2, font:get_size(text))) / 2
|
||||
--
|
||||
if bg_color then draw_onto:fill_rectangle(x0, y0, width, height, bg_color) end
|
||||
draw_centered_lines(draw_onto, lines, x0, y, width, font, font_color)
|
||||
end
|
||||
|
||||
local function choose_random_font ()
|
||||
local fonts = imlib.font.list_fonts()
|
||||
assert(#fonts > 0, 'Could not find any fonts')
|
||||
return fonts[math.random(#fonts)]
|
||||
end
|
||||
|
||||
local function load_random_font ()
|
||||
local font_name = choose_random_font()
|
||||
local font, errmsg = imlib.font.load(font_name..'/30')
|
||||
assert(font, errmsg)
|
||||
return font_name
|
||||
end
|
||||
|
||||
local function paste_topic_onto_image (target, topic, x, y, w, h, bg_color, font_name, font_color)
|
||||
assert(target)
|
||||
assert(type(topic) == 'table')
|
||||
assert(type(font_name) == 'string')
|
||||
-- Download and paste found image
|
||||
if topic.type == 'image' then
|
||||
local file_extension = topic.url:match '%.(%a+)$'
|
||||
local url, filename = topic.url, CONFIG.IMGGEN_PATH_OUTPUT..'topic_'..topic.topic..'.'..file_extension
|
||||
assert(type(url) == 'string' and #url > 0)
|
||||
assert(type(filename) == 'string' and #filename > 0)
|
||||
internet.download_file(url, filename)
|
||||
-- Convert svg to png
|
||||
if url:match '%.svg$' then
|
||||
local filename_2 = CONFIG.IMGGEN_PATH_OUTPUT..'topic_'..topic.topic..'.'..'png'
|
||||
os.execute('convert -density "1200" -resize 400x400 "'..filename..'" "'..filename_2..'"')
|
||||
filename = filename_2
|
||||
end
|
||||
--
|
||||
local found_img = assert(imlib.image.load(filename))
|
||||
found_img:crop_and_scale(0, 0, found_img:get_width(), found_img:get_height(), w, h)
|
||||
flatten_onto (target, found_img, x, y)
|
||||
found_img:free()
|
||||
--os.remove(filename)
|
||||
elseif topic.type == 'text' then
|
||||
local text = topic.text
|
||||
draw_centered_text_in_box(font_name, target, text, x, y, w, h, bg_color, font_color)
|
||||
elseif topic.type == 'droste' then
|
||||
target:fill_rectangle(x, y, w, h, COLOR_WHITE)
|
||||
else
|
||||
assert(false, topic.type)
|
||||
end
|
||||
end
|
||||
|
||||
local DROSTE_ITERATIONS_DEFAULT = 10
|
||||
|
||||
local function draw_droste_effect (target_image, droste_positions, iterations)
|
||||
assert(type(droste_positions) == 'table')
|
||||
iterations = interations or DROSTE_ITERATIONS_DEFAULT
|
||||
assert(type(iterations) == 'number')
|
||||
--
|
||||
if #droste_positions <= 0 or iterations <= 0 then return target_image end
|
||||
for _ = 1, iterations do
|
||||
for _, position in ipairs(droste_positions) do
|
||||
local paste_image = target_image:clone()
|
||||
paste_image:crop_and_scale(0, 0, paste_image:get_width(), paste_image:get_height(), position.w, position.h)
|
||||
flatten_onto (target_image, paste_image, position.x, position.y)
|
||||
paste_image:free()
|
||||
end
|
||||
end
|
||||
return target_image
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Comparison memes
|
||||
|
||||
|
||||
|
||||
local DROSTE_EFFECT_TRIGGERS = {
|
||||
['induktion'] = true,
|
||||
['rekursion'] = true,
|
||||
['uendelig rekursion'] = true,
|
||||
['rekursive'] = true,
|
||||
['rekursive funktioner'] = true,
|
||||
['recursion'] = true,
|
||||
['infinite recursion'] = true,
|
||||
['tail recursion'] = true,
|
||||
['recursive'] = true,
|
||||
['recursive function'] = true,
|
||||
['induktion'] = true,
|
||||
['droste'] = true,
|
||||
['drostle'] = true, -- Misspelling
|
||||
|
||||
['uendelig'] = true,
|
||||
['uendeligt'] = true,
|
||||
|
||||
['strange loop'] = true,
|
||||
['loop'] = true,
|
||||
}
|
||||
|
||||
local function fill_in_topics_information (topics)
|
||||
assert(type(topics) == 'table')
|
||||
--
|
||||
local topic_to_image_url = internet.search_images(topics)
|
||||
--
|
||||
local new_topics = {}
|
||||
for i, topic in ipairs(topics) do
|
||||
assert(type(topic) == 'string')
|
||||
|
||||
local url = topic_to_image_url[topic]
|
||||
|
||||
if DROSTE_EFFECT_TRIGGERS[topic] then new_topics[i] = { topic = topic, type = 'droste' }
|
||||
elseif url then new_topics[i] = { topic = topic, type = 'image', url = url }
|
||||
else new_topics[i] = { topic = topic, type = 'text', text = topic }
|
||||
end
|
||||
end
|
||||
return new_topics
|
||||
end
|
||||
|
||||
|
||||
|
||||
local function droste_positions_from_topics (topics, places)
|
||||
local droste_poss = {}
|
||||
for i, topic in ipairs(topics) do
|
||||
if topic.type == 'droste' then
|
||||
droste_poss[#droste_poss+1] = {
|
||||
x = places[i].x, y = places[i].y,
|
||||
w = places[i].w, h = places[i].h,
|
||||
}
|
||||
end
|
||||
end
|
||||
return droste_poss
|
||||
end
|
||||
|
||||
local function generate_comparison_meme_generator (positions)
|
||||
assert(type(positions) == 'table')
|
||||
assert(type(positions.base_img_path) == 'string')
|
||||
|
||||
return function (topics)
|
||||
assert(type(topics) == 'table' and #topics == #positions)
|
||||
|
||||
local font_name = choose_random_font()
|
||||
|
||||
local base_img = imlib.image.load(positions.base_img_path)
|
||||
|
||||
-- Randomize pos
|
||||
local rand_positions = {}
|
||||
for i, pos in ipairs(positions) do
|
||||
rand_positions[i] = shallow_copy(pos)
|
||||
rand_positions[i].x = pos.x + math.random(-(pos.xr or 0), pos.xr or 0)
|
||||
rand_positions[i].y = pos.y + math.random(-(pos.yr or 0), pos.yr or 0)
|
||||
end
|
||||
|
||||
-- Paste topic onto head
|
||||
for index, pos in ipairs(rand_positions) do
|
||||
paste_topic_onto_image(base_img, topics[index], pos.x, pos.y, pos.w, pos.h, nil, font_name, pos.font_color)
|
||||
end
|
||||
|
||||
-- Droste
|
||||
base_img = draw_droste_effect(base_img, droste_positions_from_topics(topics, rand_positions))
|
||||
|
||||
--
|
||||
return save_to_cloud(base_img)
|
||||
end
|
||||
end
|
||||
|
||||
local generate_distracted_boyfriend = generate_comparison_meme_generator {
|
||||
base_img_path = './images/distracted_boyfriend.jpg',
|
||||
|
||||
{ x = 640, xr = 20, y = 50, yr = 20, w = 150, h = 150 },
|
||||
{ x = 180, xr = 20, y = 50, yr = 20, w = 150, h = 150 },
|
||||
}
|
||||
|
||||
local generate_distracted_boyfriend_oldy_times = generate_comparison_meme_generator {
|
||||
base_img_path = './images/distracted_boyfriend_oldy_times.jpg',
|
||||
|
||||
{ x = 650, xr = 10, y = 100, yr = 20, w = 150, h = 150, font_color = COLOR_WHITE },
|
||||
{ x = 124, xr = 10, y = 38, yr = 10, w = 250, h = 250, font_color = COLOR_WHITE },
|
||||
}
|
||||
|
||||
local generate_drake_egon_olsen = generate_comparison_meme_generator {
|
||||
base_img_path = './images/drake_egon.png',
|
||||
|
||||
{ x = 380, xr = 0, y = 0, yr = 0, w = 380, h = 354 },
|
||||
{ x = 377, xr = 0, y = 354, yr = 0, w = 383, h = 360 },
|
||||
}
|
||||
|
||||
local generate_skorsten_image = generate_comparison_meme_generator {
|
||||
base_img_path = './images/bamse_skorsten_1.png',
|
||||
|
||||
{ x = 646, xr = 0, y = 366, yr = 0, w = 631, h = 215, font_color = COLOR_WHITE },
|
||||
}
|
||||
|
||||
local GENERATE_COMPARISON_MEME_OF_2 = {
|
||||
generate_distracted_boyfriend,
|
||||
generate_distracted_boyfriend_oldy_times,
|
||||
generate_drake_egon_olsen
|
||||
}
|
||||
|
||||
local BRAIN_ROW_HEIGHT = 150
|
||||
local BRAIN_ROW_WIDTH = 200
|
||||
|
||||
local BRAIN_MAX_EXPLOSION_TOPICS = 6
|
||||
local BRAIN_DROSTE_POS = {}
|
||||
for i = 1, BRAIN_MAX_EXPLOSION_TOPICS do
|
||||
BRAIN_DROSTE_POS[i] = {
|
||||
x = 0,
|
||||
y = (i-1) * BRAIN_ROW_HEIGHT,
|
||||
w = BRAIN_ROW_WIDTH,
|
||||
h = BRAIN_ROW_HEIGHT
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
local function generate_brain_explosion_image (topics)
|
||||
assert(type(topics) == 'table' and 1 <= #topics and #topics <= BRAIN_MAX_EXPLOSION_TOPICS)
|
||||
--
|
||||
local base_img = imlib.image.new(BRAIN_ROW_WIDTH * 2, BRAIN_ROW_HEIGHT * #topics)
|
||||
base_img:fill_rectangle(0, 0, BRAIN_ROW_WIDTH * 2, BRAIN_ROW_HEIGHT * #topics, COLOR_WHITE)
|
||||
local font_name = choose_random_font()
|
||||
--
|
||||
for i, topic_info in ipairs(topics) do
|
||||
paste_topic_onto_image(base_img, topic_info, 0, (i-1) * BRAIN_ROW_HEIGHT, BRAIN_ROW_WIDTH, BRAIN_ROW_HEIGHT, COLOR_WHITE, font_name)
|
||||
|
||||
local brain_img = imlib.image.load(CONFIG.IMGGEN_PATH_BRAINS..'brain_'..i..'.png')
|
||||
brain_img:crop_and_scale(0, 0, brain_img:get_width(), brain_img:get_height(), BRAIN_ROW_WIDTH, BRAIN_ROW_HEIGHT)
|
||||
flatten_onto (base_img, brain_img, BRAIN_ROW_WIDTH, (i-1) * BRAIN_ROW_HEIGHT)
|
||||
brain_img:free()
|
||||
end
|
||||
|
||||
-- Droste
|
||||
base_img = draw_droste_effect(base_img, droste_positions_from_topics(topics, BRAIN_DROSTE_POS))
|
||||
|
||||
-- Save
|
||||
return save_to_cloud(base_img)
|
||||
end
|
||||
|
||||
|
||||
|
||||
local function is_comparison_message (message)
|
||||
local leq, gep = message:match '<', message:match '>'
|
||||
return leq and not gep and '<' or not leq and gep and '>'
|
||||
end
|
||||
|
||||
local COMP_SENTENCES do
|
||||
local ORD_MED_ATTITYDE = {
|
||||
bedre = 'positiv',
|
||||
dårligere = 'negativ',
|
||||
hurtigere = 'positiv',
|
||||
langsommere = 'negativt',
|
||||
robust = 'positivt',
|
||||
}
|
||||
COMP_SENTENCES = {}
|
||||
for ord, attityde in pairs(ORD_MED_ATTITYDE) do
|
||||
local first, second = '{A}', '{B}'
|
||||
if attityde == 'negativ' then first, second = second, first end
|
||||
for _, fmt in ipairs { '%s er %s end %s', '%s er mere %s end %s', '%s er meget %s end %s', '%s %s end %s' } do
|
||||
table.insert(COMP_SENTENCES, fmt:format(first, ord, second))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for sentence_i, sentence in ipairs(COMP_SENTENCES) do
|
||||
local A_pos, B_pos = sentence:find '{A}', sentence:find '{B}'
|
||||
local best_first = (A_pos < B_pos)
|
||||
local pattern_sentence = sentence:gsub('{[AB]}', '(.+)'):gsub('%s+','%%s+')
|
||||
COMP_SENTENCES[pattern_sentence] = { best_first = best_first }
|
||||
COMP_SENTENCES[sentence_i] = nil
|
||||
end
|
||||
|
||||
local function get_topics_from_comparison_message(message)
|
||||
local comp = is_comparison_message(message)
|
||||
if comp then
|
||||
local topics = {}
|
||||
for topic in message:gmatch('[^'..comp..']+') do
|
||||
local topic_text = clean_text(topic)
|
||||
if topic_text:match '[^%s]' then
|
||||
topics[#topics+1] = topic_text
|
||||
end
|
||||
end
|
||||
-- Rev
|
||||
if comp == '>' then topics = reverse(topics) end
|
||||
--
|
||||
return (#topics >= 2) and topics or nil
|
||||
end
|
||||
|
||||
-- Natural language comparisons
|
||||
for pattern, sentence_info in pairs(COMP_SENTENCES) do
|
||||
local good, bad = message:lower():match(pattern)
|
||||
if not sentence_info.best_first then good, bad = bad, good end
|
||||
if good then return { bad, good } end
|
||||
end
|
||||
|
||||
--
|
||||
return nil
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- Other memes
|
||||
|
||||
local function generate_bait_link()
|
||||
return 'https://dcav.pw/jbait'
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
function memes.generate_for_message (user, message)
|
||||
|
||||
-- Bait msg
|
||||
if message:match '%f[%a]bait%f[%A]' then
|
||||
return generate_bait_link(), 'BAIT'
|
||||
end
|
||||
|
||||
-- Meme machine
|
||||
if message:match '%f[%a]meme%s+machine%f[%A]' then
|
||||
return 'https://www.youtube.com/watch?v=wl-LeTFM8zo', 'MACHINE'
|
||||
end
|
||||
|
||||
-- Vælter min skorsten
|
||||
do
|
||||
local problem_text = message:lower():match 'vælter%s+min%s+skorsten%s*(.+)$'
|
||||
if problem_text then
|
||||
if problem_text:match '^er' then problem_text = problem_text:match '^er%s*(.+)$' end
|
||||
if problem_text:match '^:' then problem_text = problem_text:match '^:%s*(.+)$' end
|
||||
local img_link = generate_skorsten_image {{ type = 'text', text = problem_text }}
|
||||
return img_link, 'SKRSTEN'
|
||||
end
|
||||
end
|
||||
|
||||
-- Comparison memes
|
||||
local topics = get_topics_from_comparison_message(message)
|
||||
if not topics then return end
|
||||
|
||||
local topics = fill_in_topics_information(topics)
|
||||
--
|
||||
assert(#topics >= 2)
|
||||
local img_link = (#topics == 2) and choose(GENERATE_COMPARISON_MEME_OF_2)(topics)
|
||||
or generate_brain_explosion_image(topics)
|
||||
--
|
||||
return img_link, 'COMPAR'
|
||||
end
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
return memes
|
||||
|
Loading…
Reference in New Issue
Block a user