Fix logger to properly log everything when debug is on

-- Copyright (c) 2014 by Adam Hellberg <sharparam@sharparam.com>.
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in
-- all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-- THE SOFTWARE.

NAME, T = ...

import decorate from T.misc

{localization: L} = T

import format, upper from string

local db

logging_enabled = true
logging_level = 1
logging_color_enabled = true
db_loaded = false
debug_enabled = false

class T.Logger
    @levels:
        DEBUG: 0
        INFO: 1
        WARN: 2
        ERROR: 3
        FATAL: 4
        NOTICE: 5

    @level_to_prefix: (level) =>
        @prefixes[level] or L.unknown\upper!

    @prefix_to_level: (prefix) =>
        prefix = prefix\upper!
        @levels[prefix] or -1

    new: (@name, @color) =>

    log: (level, ...) =>
        return unless logging_enabled -- db 'log', true
        return if level == @@levels.DEBUG and not debug_enabled
        return if level < logging_level and not debug_enabled
        prefix = @@level_to_prefix level
        docolor = logging_color_enabled
        name = docolor and decorate(NAME, @@colors.name) or NAME
        logname = (docolor and @color) and decorate(@name, @color) or @name
        prefix_color = docolor and @@colors.levels[level] or nil
        prefix = (docolor and prefix_color) and decorate(prefix, prefix_color) or prefix
        msg = '%s.%s: [%s] %s'\format name, logname, prefix, format ...
        DEFAULT_CHAT_FRAME\AddMessage msg

import Logger from T

Logger.prefixes = {v, k for k, v in pairs Logger.levels}

with Logger
    .colors =
        name: '00FF00' -- -> db 'log.color.name', '00FF00'
        levels:
            [.levels.DEBUG]: '008000' -- -> db 'log.color.level.debug',  '008000' -- Green
            [.levels.INFO]: 'FFFFFF'  -- -> db 'log.color.level.info',   'FFFFFF' -- White
            [.levels.WARN]: 'FFD700' --  -> db 'log.color.level.warn',   'FFD700' -- Gold
            [.levels.ERROR]: 'FF0000' -- -> db 'log.color.level.error',  'FF0000' -- Red
            [.levels.FATAL]: 'FF0000' -- -> db 'log.color.level.fatal',  'FF0000' -- Red
            [.levels.NOTICE]: '00FFFF' -- -> db 'log.color.level.notice', '00FFFF' -- Cyan

for id, level in pairs Logger.levels
    Logger.__base[id\lower!] = (...) => @log level, ...

log = Logger('main')

T.log = log

load_db = (event, database) ->

T.SHAREXP_DB_CREATED = (event, database) ->
    db = database if database.global_name == 'ShareXPDB'

T.SHAREXP_DB_LOADED = (event, database) ->
    return unless database.global_name == db.global_name
    logging_enabled = db 'log', logging_enabled
    logging_level = db 'log.level', logging_level
    logging_color_enabled = db 'log.color', logging_color_enabled
    db_loaded = true
    debug_enabled = db 'debug', debug_enabled
    for level, color in pairs Logger.colors.levels
        prefix = Logger\level_to_prefix(level)\lower!
        Logger.colors.levels[level] = db "log.color.level.#{prefix}", color

T.SHAREXP_DB_UPDATED = (event, db, key) ->
    return unless db.global_name == 'ShareXPDB'
    switch key
        when 'debug'
            debug_enabled = db 'debug'
        when 'log'
            value = db 'log'
            logging_enabled = value
            log\notice L.log_status_changed, value and L.enabled or L.disabled
        when 'log.color'
            value = db 'log.color'
            logging_color_enabled = value
            log\notice L.log_coloring_changed, value and L.enabled or L.disabled
        when 'log.level'
            value = db 'log.level'
            logging_level = value
            log\notice L.log_level_changed, Logger\level_to_prefix value
        when 'log.color.name'
            color = db 'log.color.name'
            Logger.colors.name = color
            msg = L 'log_color_changed', L.name\gsub('^.', upper), color
            log\notice decorate(msg, color)
        else
            prefix = key\match '^log%.color%.level%.(%w+)$'
            if prefix
                prefix = prefix\upper!
                level = Logger\prefix_to_level prefix
                color = db key
                Logger.colors.levels[level] = color
                msg = L 'log_color_changed', prefix, color
                log\notice decorate(msg, color)

-- Copyright (c) 2014 by Adam Hellberg <sharparam@sharparam.com>.
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy
-- of this software and associated documentation files (the "Software"), to deal
-- in the Software without restriction, including without limitation the rights
-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-- copies of the Software, and to permit persons to whom the Software is
-- furnished to do so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in
-- all copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-- THE SOFTWARE.

NAME, T = ...

import decorate from T.misc

{localization: L} = T

import format, upper from string

local db

logging_enabled = true
logging_level = 1
logging_color_enabled = true
db_loaded = false
debug_enabled = false

class T.Logger
    @levels:
        DEBUG: 0
        INFO: 1
        WARN: 2
        ERROR: 3
        FATAL: 4
        NOTICE: 5

    @level_to_prefix: (level) =>
        @prefixes[level] or L.unknown\upper!

    @prefix_to_level: (prefix) =>
        prefix = prefix\upper!
        @levels[prefix] or -1

    new: (@name, @color) =>

    log: (level, ...) =>
        return unless logging_enabled -- db 'log', true
        return if level == @@levels.DEBUG and (not db_loaded or not debug_enabled)
        -- TODO: Review why the fuck the below was there
        -- Override the logging level if global debug option is enabled
        --level = @@levels.DEBUG if debug_enabled
        return if level < logging_level
        prefix = @@level_to_prefix level
        docolor = logging_color_enabled
        name = docolor and decorate(NAME, @@colors.name) or NAME
        logname = (docolor and @color) and decorate(@name, @color) or @name
        prefix_color = docolor and @@colors.levels[level] or nil
        prefix = (docolor and prefix_color) and decorate(prefix, prefix_color) or prefix
        msg = '%s.%s: [%s] %s'\format name, logname, prefix, format ...
        DEFAULT_CHAT_FRAME\AddMessage msg

import Logger from T

Logger.prefixes = {v, k for k, v in pairs Logger.levels}

with Logger
    .colors =
        name: '00FF00' -- -> db 'log.color.name', '00FF00'
        levels:
            [.levels.DEBUG]: '008000' -- -> db 'log.color.level.debug',  '008000' -- Green
            [.levels.INFO]: 'FFFFFF'  -- -> db 'log.color.level.info',   'FFFFFF' -- White
            [.levels.WARN]: 'FFD700' --  -> db 'log.color.level.warn',   'FFD700' -- Gold
            [.levels.ERROR]: 'FF0000' -- -> db 'log.color.level.error',  'FF0000' -- Red
            [.levels.FATAL]: 'FF0000' -- -> db 'log.color.level.fatal',  'FF0000' -- Red
            [.levels.NOTICE]: '00FFFF' -- -> db 'log.color.level.notice', '00FFFF' -- Cyan

for id, level in pairs Logger.levels
    Logger.__base[id\lower!] = (...) => @log level, ...

log = Logger('main')

T.log = log

load_db = (event, database) ->

T.SHAREXP_DB_CREATED = (event, database) ->
    db = database if database.global_name == 'ShareXPDB'

T.SHAREXP_DB_LOADED = (event, database) ->
    return unless database.global_name == db.global_name
    logging_enabled = db 'log', logging_enabled
    logging_level = db 'log.level', logging_level
    logging_color_enabled = db 'log.color', logging_color_enabled
    db_loaded = true
    debug_enabled = db 'debug', debug_enabled
    for level, color in pairs Logger.colors.levels
        prefix = Logger\level_to_prefix(level)\lower!
        Logger.colors.levels[level] = db "log.color.level.#{prefix}", color

T.SHAREXP_DB_UPDATED = (event, db, key) ->
    return unless db.global_name == 'ShareXPDB'
    switch key
        when 'debug'
            debug_enabled = db 'debug'
        when 'log'
            value = db 'log'
            logging_enabled = value
            log\notice L.log_status_changed, value and L.enabled or L.disabled
        when 'log.color'
            value = db 'log.color'
            logging_color_enabled = value
            log\notice L.log_coloring_changed, value and L.enabled or L.disabled
        when 'log.level'
            value = db 'log.level'
            logging_level = value
            log\notice L.log_level_changed, Logger\level_to_prefix value
        when 'log.color.name'
            color = db 'log.color.name'
            Logger.colors.name = color
            msg = L 'log_color_changed', L.name\gsub('^.', upper), color
            log\notice decorate(msg, color)
        else
            prefix = key\match '^log%.color%.level%.(%w+)$'
            if prefix
                prefix = prefix\upper!
                level = Logger\prefix_to_level prefix
                color = db key
                Logger.colors.levels[level] = color
                msg = L 'log_color_changed', prefix, color
                log\notice decorate(msg, color)


Fix function invocation

Fix function invocation

Texture   = sauce3.java.require "com.badlogic.gdx.graphics.Texture"
Constants = require "sauce3.wrappers"

class
  new: (path, format = "rgba8", f_type) =>
    file = sauce3.File path, f_type

    @texture = sauce3.java.new Texture, file.file, Constants.formats[format], false
    @texture\setFilter Constants.filters["linear"], Constants.filters["linear"]

  get_dimensions: =>
    @get_width!, @get_height!

  get_filter: =>
    min_filter = @texture\getMinFilter!
    mag_filter = @texture\getMagFilter!

    Constants.filter_codes[min_filter], Constants.filter_codes[mag_filter]

  get_format: =>
    texture_data = @texture\getTextureData!
    format       = texture_data\getFormat!

    Constants.format_codes[format]

  get_width: =>
    @texture\get_width!

  get_height: =>
    @texture\getHeight!

  get_wrap: =>
    Constants.wraps[@texture\getUWrap!], Constants.wraps[@texture\getVWrap!]

  set_filter: (min, mag) =>
    @texture\setFilter Constants.filters[min], Constants.filters[mag]

  set_wrap: (h, v) =>
    @texture\setWrap Constants.wraps[h], Constants.wraps[v]

  dispose: =>
    @texture\dispose!

Texture   = sauce3.java.require "com.badlogic.gdx.graphics.Texture"
Constants = require "sauce3.wrappers"

class
  new: (path, format = "rgba8", f_type) =>
    file = sauce3.File path, f_type

    @texture = sauce3.java.new Texture, file.file, Constants.formats[format], false
    @texture\setFilter Constants.filters["linear"], Constants.filters["linear"]

  get_dimensions: =>
    @get_width!, @get_height!

  get_filter: =>
    min_filter = @texture\getMinFilter!
    mag_filter = @texture\getMagFilter!

    Constants.filter_codes[min_filter], Constants.filter_codes[mag_filter]

  get_format: =>
    texture_data = @texture\getTextureData!
    format       = texture_data\getFormat!

    Constants.format_codes[format]

  get_width: =>
    @texture\get_width

  get_height: =>
    @texture\getHeight!

  get_wrap: =>
    Constants.wraps[@texture\getUWrap!], Constants.wraps[@texture\getVWrap!]

  set_filter: (min, mag) =>
    @texture\setFilter Constants.filters[min], Constants.filters[mag]

  set_wrap: (h, v) =>
    @texture\setWrap Constants.wraps[h], Constants.wraps[v]

  dispose: =>
    @texture\dispose!


duckduckgo: Fix trailing whitespace.

duckduckgo: Fix trailing whitespace.

Former-commit-id: 071bf5a3adf399814454a7fd8b3837037b7fd6b4 [formerly 636cf469aa8c9e04e7483101f734b9c8c205cfc4]
Former-commit-id: c70f2e25953a7f49fb2d1d96c503fb98b9b40d55

json = require 'json'
simplehttp = require 'simplehttp'

urlEncode = (str, space) ->
  space = space or '+'

  str = str\gsub '([^%w ])', (c) ->
      string.format  "%%%02X", string.byte(c)
  return str\gsub(' ', space)

PRIVMSG:
  '^%pddg (.+)$': (source, destination, term) =>
    term = urlEncode term
    simplehttp "http://api.duckduckgo.com/?q=#{term}&format=json", (d) ->
      data = json.decode d
      out = {}
      topic = data.RelatedTopics[1] if data.RelatedTopics and #data.RelatedTopics>0
      if data.Heading == ''
        return
      table.insert out, "\002#{data.Heading}\002:"
      table.insert out, data.AbstractText unless data.AbstractText == ''
      table.insert out, data.Image unless data.Image == ''
      table.insert out, data.AbstractURL unless data.AbstractURL == ''
      table.insert out, data.Definition unless data.Definition == ''
      table.insert out, data.DefinitionText unless data.DefinitionText == ''
      if #out < 3
        table.insert out, topic.FirstURL
        table.insert out, topic.Text

      say table.concat(out, ' ')


Move saves to NBT

Move saves to NBT

--
-- Copyright (C) 2017-2018 DBot
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--

file.CreateDir('ppm2')
file.CreateDir('ppm2/backups')
file.CreateDir('ppm2/thumbnails')

for ffind in *file.Find('ppm2/*.txt', 'DATA')
	fTarget = ffind\sub(1, -5)
	fRead = file.Read('ppm2/' .. ffind, 'DATA')
	json = util.JSONToTable(fRead)
	if json
		TagCompound = DLib.NBT.TagCompound()
		for key, value in pairs json
			switch type(value)
				when 'string'
					TagCompound\AddString(key, value)
				when 'number'
					TagCompound\AddFloat(key, value)
				when 'boolean'
					TagCompound\AddByte(key, value and 1 or 0)
				when 'table'
					-- assume color
					TagCompound\AddByteArray(key, {value.r - 128, value.g - 128, value.b - 128, value.a - 128}) if value.r and value.g and value.b and value.a
				else
					error(type(value))
		buf = DLib.BytesBuffer()
		TagCompound\WriteFile(buf)
		file.Write('ppm2/' .. fTarget .. '.dat', buf\ToString())
	file.Delete('ppm2/' .. ffind)

class PonyDataInstance
	@DATA_DIR = "ppm2/"
	@DATA_DIR_BACKUP = "ppm2/backups/"

	@FindFiles = =>
		output = [str\sub(1, #str - 4) for str in *file.Find(@DATA_DIR .. '*', 'DATA') when not str\find('.bak.txt')]
		return output

	@FindInstances = =>
		output = [@(str\sub(1, #str - 4)) for str in *file.Find(@DATA_DIR .. '*', 'DATA') when not str\find('.bak.txt')]
		return output

	@PONY_DATA = PPM2.PonyDataRegistry

	@PONY_DATA_MAPPING = {getFunc\lower(), key for key, {:getFunc} in pairs @PONY_DATA}
	@PONY_DATA_MAPPING[key] = key for key, value in pairs @PONY_DATA

	for key, data in pairs @PONY_DATA
		continue unless data.enum
		data.enum = [arg\upper() for arg in *data.enum]
		data.enumMapping = {}
		data.enumMappingBackward = {}
		i = -1
		for enumVal in *data.enum
			i += 1
			data.enumMapping[i] = enumVal
			data.enumMappingBackward[enumVal] = i
	for key, {:getFunc, :fix, :enumMappingBackward, :enumMapping, :enum, :min, :max, :default} in pairs @PONY_DATA
		@__base["Get#{getFunc}"] = => @dataTable[key]
		@__base["GetMin#{getFunc}"] = => min if min
		@__base["GetMax#{getFunc}"] = => max if max
		@__base["Enum#{getFunc}"] = => enum if enum
		@__base["Get#{getFunc}Types"] = => enum if enum

		@["GetMin#{getFunc}"] = => min if min
		@["GetMax#{getFunc}"] = => max if max
		@["GetDefault#{getFunc}"] = default
		@["GetEnum#{getFunc}"] = => enum if enum
		@["Enum#{getFunc}"] = => enum if enum

		if enumMapping
			@__base["Get#{getFunc}Enum"] = => enumMapping[@dataTable[key]] or enumMapping[0] or @dataTable[key]
			@__base["GetEnum#{getFunc}"] = @__base["Get#{getFunc}Enum"]
		@__base["Reset#{getFunc}"] = => @["Set#{getFunc}"](@, default())
		@__base["Set#{getFunc}"] = (val = defValue, ...) =>
			if type(val) == 'string' and enumMappingBackward
				newVal = enumMappingBackward[val\upper()]
				val = newVal if newVal
			newVal = fix(val)
			oldVal = @dataTable[key]
			@dataTable[key] = newVal
			@ValueChanges(key, oldVal, newVal, ...)

	WriteNetworkData: =>
		for {:strName, :writeFunc, :getName, :defValue} in *PPM2.NetworkedPonyData.NW_Vars
			if @["Get#{getName}"]
				writeFunc(@["Get#{getName}"](@))
			else
				writeFunc(defValue)

	Copy: (fileName = @filename) =>
		copyOfData = {}
		for key, val in pairs @dataTable
			switch type(val)
				when 'number'
					copyOfData[key] = val
				when 'string'
					copyOfData[key] = val
				when 'boolean'
					copyOfData[key] = val
				when 'table'
					{:r, :g, :b} = val
					if r and g and b
						copyOfData[key] = Color(r, g, b)
		newData = @@(fileName, copyOfData, false)
		return newData
	CreateCustomNetworkObject: (goingToNetwork = false, ply = LocalPlayer(), ...) =>
		newData = PPM2.NetworkedPonyData(nil, ply)
		newData\SetIsGoingToNetwork(goingToNetwork)
		newData\SetEntity(ply)
		@ApplyDataToObject(newData, ...)
		return newData
	CreateNetworkObject: (goingToNetwork = true, ...) =>
		newData = PPM2.NetworkedPonyData(nil, LocalPlayer())
		newData\SetIsGoingToNetwork(goingToNetwork)
		newData\SetEntity(LocalPlayer())
		@ApplyDataToObject(newData, ...)
		return newData
	ApplyDataToObject: (target, ...) =>
		for key, value in pairs @GetAsNetworked()
			error("Attempt to apply data to object #{target} at unknown index #{key}!") if not target["Set#{key}"]
			target["Set#{key}"](target, value, ...)
	UpdateController: (...) => @ApplyDataToObject(@nwObj, ...)
	CreateController: (...) => @CreateNetworkObject(false, ...)
	CreateCustomController: (...) => @CreateCustomNetworkObject(false, ...)

	new: (filename, data, readIfExists = true, force = false, doBackup = true) =>
		@SetFilename(filename)
		@NBTTagCompound = DLib.NBT.TagCompound()
		@updateNWObject = true
		@networkNWObject = true
		@valid = @isOpen
		@rawData = data
		@dataTable = {k, default() for k, {:default} in pairs @@PONY_DATA}
		@saveOnChange = true
		if data
			@SetupData(data, true)
		elseif @exists and readIfExists
			@ReadFromDisk(force, doBackup)

	Reset: => @['Reset' .. getFunc](@) for k, {:getFunc} in pairs @@PONY_DATA

	@ERR_MISSING_PARAMETER = 4
	@ERR_MISSING_CONTENT = 5

	GetSaveOnChange: => @saveOnChange
	SaveOnChange: => @saveOnChange
	SetSaveOnChange: (val = true) => @saveOnChange = val
	GetValueFromNBT: (mapData, value) =>
		if mapData.enum and type(value) == 'string'
			mapData.fix(mapData.enumMappingBackward[value\upper()])
		elseif mapData.type == 'COLOR'
			if value.r and value.g and value.b and value.a
				mapData.fix(Color(value))
			else
				mapData.fix(Color(value[1] + 128, value[2] + 128, value[3] + 128, value[4] + 128))
		elseif mapData.type == 'BOOLEAN'
			if type(value) == 'boolean'
				mapData.fix(value)
			else
				mapData.fix(value == 1)
		else
			mapData.fix(value)

	SetupData: (data = @NBTTagCompound, force = false, doBackup = false) =>
		if type(data) == 'NBTCompound'
			data = data\GetValue()
		if doBackup or not force
			makeBackup = false
			for key, value2 in pairs(data)
				key = key\lower()
				map = @@PONY_DATA_MAPPING[key]
				if map
					mapData = @@PONY_DATA[map]
					value = @GetValueFromNBT(mapData, value2)
					if mapData.enum
						if type(value) == 'string' and not mapData.enumMappingBackward[value\upper()] or type(value) == 'number' and not mapData.enumMapping[value]
							return @@ERR_MISSING_CONTENT if not force
							makeBackup = true
							break
			if doBackup and makeBackup and @exists
				bkName = "#{@@DATA_DIR_BACKUP}#{@filename}_bak_#{os.date('%S_%M_%H-%d_%m_%Y', os.time())}.dat"
				fRead = file.Read(@fpath, 'DATA')
				file.Write(bkName, fRead)

		for key, value2 in pairs(data)
			key = key\lower()
			map = @@PONY_DATA_MAPPING[key]
			continue unless map
			mapData = @@PONY_DATA[map]
			@dataTable[key] = @GetValueFromNBT(mapData, value2)
	ValueChanges: (key, oldVal, newVal, saveNow = @exists and @saveOnChange) =>
		if @nwObj and @updateNWObject
			{:getFunc} = @@PONY_DATA[key]
			@nwObj["Set#{getFunc}"](@nwObj, newVal, @networkNWObject)
		@Save() if saveNow
	SetFilename: (filename) =>
		@filename = filename
		@filenameFull = "#{filename}.dat"
		@fpath = "#{@@DATA_DIR}#{filename}.dat"
		@preview = "#{@@DATA_DIR}thumbnails/#{filename}.png"
		@fpathBackup = "#{@@DATA_DIR}#{filename}.bak.dat"
		@fpathFull = "data/#{@@DATA_DIR}#{filename}.dat"
		@isOpen = @filename ~= nil
		@exists = file.Exists(@fpath, 'DATA')
		return @exists
	SetNetworkData: (nwObj) => @nwObj = nwObj
	SetPonyData: (nwObj) => @nwObj = nwObj
	SetPonyDataController: (nwObj) => @nwObj = nwObj
	SetPonyController: (nwObj) => @nwObj = nwObj
	SetController: (nwObj) => @nwObj = nwObj
	SetDataController: (nwObj) => @nwObj = nwObj

	SetNetworkOnChange: (newVal = true) => @networkNWObject = newVal
	SetUpdateOnChange: (newVal = true) => @updateNWObject = newVal

	GetNetworkOnChange: => @networkNWObject
	GetUpdateOnChange: => @updateNWObject

	GetNetworkData: => @nwObj
	GetPonyData: => @nwObj
	GetPonyDataController: => @nwObj
	GetPonyController: => @nwObj
	GetController: => @nwObj
	GetDataController: => @nwObj

	IsValid: => @valid
	Exists: => @exists
	FileExists: => @exists
	IsExists: => @exists
	GetFileName: => @filename
	GetFilename: => @filename
	GetFileNameFull: => @filenameFull
	GetFilenameFull: => @filenameFull
	GetFilePath: => @fpath
	GetFullFilePath: => @fpathFull
	SerealizeValue: (valID = '') =>
		map = @@PONY_DATA[valID]
		return unless map
		val = @dataTable[valID]
		if map.enum
			return DLib.NBT.TagString(map.enumMapping[val] or map.enumMapping[map.default()])
		elseif map.serealize
			return map.serealize(val)
		else
			switch map.type
				when 'INT'
					return DLib.NBT.TagInt(val)
				when 'FLOAT'
					return DLib.NBT.TagFloat(val)
				when 'URL'
					return DLib.NBT.TagString(val)
				when 'BOOLEAN'
					return DLib.NBT.TagByte(tonumber(val))
				when 'COLOR'
					return DLib.NBT.TagByteArray({val.r - 128, val.g - 128, val.b - 128, val.a - 128})
	GetAsNetworked: => {getFunc, @dataTable[k] for k, {:getFunc} in pairs @@PONY_DATA}

	@READ_SUCCESS = 0
	@ERR_FILE_NOT_EXISTS = 1
	@ERR_FILE_EMPTY = 2
	@ERR_FILE_CORRUPT = 3
	ReadFromDisk: (force = false, doBackup = true) =>
		return @@ERR_FILE_NOT_EXISTS unless @exists
		fRead = file.Read(@fpath, 'DATA')
		return @@ERR_FILE_EMPTY if not fRead or fRead == ''
		@NBTTagCompound\ReadFile(DLib.BytesBuffer(fRead))
		return @SetupData(@NBTTagCompound, force, doBackup) or @@READ_SUCCESS

	SaveAs: (path = @fpath) =>
		@NBTTagCompound\AddTag(key, @SerealizeValue(key)) for key, val in pairs @dataTable
		buf = DLib.BytesBuffer()
		@NBTTagCompound\WriteFile(buf)
		file.Write(path, buf\ToString())
		return buf

	SavePreview: (path = @preview) =>
		buildingModel = ClientsideModel('models/ppm/ppm2_stage.mdl', RENDERGROUP_OTHER)
		buildingModel\SetNoDraw(true)
		buildingModel\SetModelScale(0.9)
		model = ClientsideModel('models/ppm/player_default_base_new_nj.mdl')

		with model
			\SetNoDraw(true)
			data = @CreateCustomController(model)
			.__PPM2_PonyData = data
			ctrl = data\GetRenderController()

			if bg = data\GetBodygroupController()
				bg\ApplyBodygroups()

			with model\PPMBonesModifier()
				\ResetBones()
				hook.Call('PPM2.SetupBones', nil, model, data)
				\Think(true)

			\SetSequence(22)
			\FrameAdvance(0)

		timer.Simple 0.5, ->
			renderTarget = GetRenderTarget('ppm2_save_preview_generate2', 1024, 1024, false)
			renderTarget\Download()
			render.PushRenderTarget(renderTarget)
			--render.SuppressEngineLighting(true)
			render.Clear(0, 0, 0, 255, true, true)
			cam.Start3D(Vector(49.373046875, -35.021484375, 58.332901000977), Angle(0, 141, 0), 90, 0, 0, 1024, 1024)

			buildingModel\DrawModel()

			with model
				data = .__PPM2_PonyData
				ctrl = data\GetRenderController()

				if bg = data\GetBodygroupController()
					bg\ApplyBodygroups()

				with model\PPMBonesModifier()
					\ResetBones()
					hook.Call('PPM2.SetupBones', nil, model, data)
					\Think(true)

				with ctrl
					\DrawModels()
					\HideModels(true)
					\PreDraw(model, true)

				\DrawModel()
				ctrl\PostDraw(model, true)

			cam.End3D()

			data = render.Capture({
				format: 'png'
				x: 0
				y: 0
				w: 1024
				h: 1024
				alpha: false
			})

			model\Remove()
			buildingModel\Remove()

			file.Write(path, data)

			--render.SuppressEngineLighting(false)
			render.PopRenderTarget()

	Save: (doBackup = true, preview = true) =>
		if doBackup and @exists
			fRead = file.Read(@fpath, 'DATA')
			file.Write(@fpathBackup, fRead)
		buf = @SaveAs(@fpath)
		@SavePreview(@preview) if preview
		@exists = true
		return buf

PPM2.PonyDataInstance = PonyDataInstance

PPM2.MainDataInstance = nil
PPM2.GetMainData = ->
	if not PPM2.MainDataInstance
		PPM2.MainDataInstance = PonyDataInstance('_current', nil, true, true)
		if not PPM2.MainDataInstance\FileExists()
			PPM2.MainDataInstance\Save()
	return PPM2.MainDataInstance

--
-- Copyright (C) 2017-2018 DBot
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--

file.CreateDir('ppm2')
file.CreateDir('ppm2/backups')
file.CreateDir('ppm2/thumbnails')

class PonyDataInstance
	@DATA_DIR = "ppm2/"
	@DATA_DIR_BACKUP = "ppm2/backups/"

	@FindFiles = =>
		output = [str\sub(1, #str - 4) for str in *file.Find(@DATA_DIR .. '*', 'DATA') when not str\find('.bak.txt')]
		return output

	@FindInstances = =>
		output = [@(str\sub(1, #str - 4)) for str in *file.Find(@DATA_DIR .. '*', 'DATA') when not str\find('.bak.txt')]
		return output

	@PONY_DATA = PPM2.PonyDataRegistry

	@PONY_DATA_MAPPING = {getFunc\lower(), key for key, {:getFunc} in pairs @PONY_DATA}
	@PONY_DATA_MAPPING[key] = key for key, value in pairs @PONY_DATA

	for key, data in pairs @PONY_DATA
		continue unless data.enum
		data.enum = [arg\upper() for arg in *data.enum]
		data.enumMapping = {}
		data.enumMappingBackward = {}
		i = -1
		for enumVal in *data.enum
			i += 1
			data.enumMapping[i] = enumVal
			data.enumMappingBackward[enumVal] = i
	for key, {:getFunc, :fix, :enumMappingBackward, :enumMapping, :enum, :min, :max, :default} in pairs @PONY_DATA
		@__base["Get#{getFunc}"] = => @dataTable[key]
		@__base["GetMin#{getFunc}"] = => min if min
		@__base["GetMax#{getFunc}"] = => max if max
		@__base["Enum#{getFunc}"] = => enum if enum
		@__base["Get#{getFunc}Types"] = => enum if enum

		@["GetMin#{getFunc}"] = => min if min
		@["GetMax#{getFunc}"] = => max if max
		@["GetDefault#{getFunc}"] = default
		@["GetEnum#{getFunc}"] = => enum if enum
		@["Enum#{getFunc}"] = => enum if enum

		if enumMapping
			@__base["Get#{getFunc}Enum"] = => enumMapping[@dataTable[key]] or enumMapping[0] or @dataTable[key]
			@__base["GetEnum#{getFunc}"] = @__base["Get#{getFunc}Enum"]
		@__base["Reset#{getFunc}"] = => @["Set#{getFunc}"](@, default())
		@__base["Set#{getFunc}"] = (val = defValue, ...) =>
			if type(val) == 'string' and enumMappingBackward
				newVal = enumMappingBackward[val\upper()]
				val = newVal if newVal
			newVal = fix(val)
			oldVal = @dataTable[key]
			@dataTable[key] = newVal
			@ValueChanges(key, oldVal, newVal, ...)

	WriteNetworkData: =>
		for {:strName, :writeFunc, :getName, :defValue} in *PPM2.NetworkedPonyData.NW_Vars
			if @["Get#{getName}"]
				writeFunc(@["Get#{getName}"](@))
			else
				writeFunc(defValue)

	Copy: (fileName = @filename) =>
		copyOfData = {}
		for key, val in pairs @dataTable
			switch type(val)
				when 'number'
					copyOfData[key] = val
				when 'string'
					copyOfData[key] = val
				when 'boolean'
					copyOfData[key] = val
				when 'table'
					{:r, :g, :b} = val
					if r and g and b
						copyOfData[key] = Color(r, g, b)
		newData = @@(fileName, copyOfData, false)
		return newData
	CreateCustomNetworkObject: (goingToNetwork = false, ply = LocalPlayer(), ...) =>
		newData = PPM2.NetworkedPonyData(nil, ply)
		newData\SetIsGoingToNetwork(goingToNetwork)
		newData\SetEntity(ply)
		@ApplyDataToObject(newData, ...)
		return newData
	CreateNetworkObject: (goingToNetwork = true, ...) =>
		newData = PPM2.NetworkedPonyData(nil, LocalPlayer())
		newData\SetIsGoingToNetwork(goingToNetwork)
		newData\SetEntity(LocalPlayer())
		@ApplyDataToObject(newData, ...)
		return newData
	ApplyDataToObject: (target, ...) =>
		for key, value in pairs @GetAsNetworked()
			error("Attempt to apply data to object #{target} at unknown index #{key}!") if not target["Set#{key}"]
			target["Set#{key}"](target, value, ...)
	UpdateController: (...) => @ApplyDataToObject(@nwObj, ...)
	CreateController: (...) => @CreateNetworkObject(false, ...)
	CreateCustomController: (...) => @CreateCustomNetworkObject(false, ...)

	new: (filename, data, readIfExists = true, force = false, doBackup = true) =>
		@SetFilename(filename)
		@updateNWObject = true
		@networkNWObject = true
		@valid = @isOpen
		@rawData = data
		@dataTable = {k, default() for k, {:default} in pairs @@PONY_DATA}
		@saveOnChange = true
		if data
			@SetupData(data, true)
		elseif @exists and readIfExists
			@ReadFromDisk(force, doBackup)

	Reset: => @['Reset' .. getFunc](@) for k, {:getFunc} in pairs @@PONY_DATA

	@ERR_MISSING_PARAMETER = 4
	@ERR_MISSING_CONTENT = 5

	GetSaveOnChange: => @saveOnChange
	SaveOnChange: => @saveOnChange
	SetSaveOnChange: (val = true) => @saveOnChange = val
	SetupData: (data, force = false, doBackup = false) =>
		if doBackup or not force
			makeBackup = false
			for key, value in pairs data
				key = key\lower()
				map = @@PONY_DATA_MAPPING[key]
				if not map
					return @@ERR_MISSING_PARAMETER if not force
					makeBackup = true
					break
				mapData = @@PONY_DATA[map]
				if mapData.enum
					if type(value) == 'string' and not mapData.enumMappingBackward[value\upper()] or type(value) == 'number' and not mapData.enumMapping[value]
						return @@ERR_MISSING_CONTENT if not force
						makeBackup = true
						break
			if doBackup and makeBackup and @exists
				bkName = "#{@@DATA_DIR_BACKUP}#{@filename}_bak_#{os.date('%S_%M_%H-%d_%m_%Y', os.time())}.txt"
				fRead = file.Read(@fpath, 'DATA')
				file.Write(bkName, fRead)

		for key, value in pairs data
			key = key\lower()
			map = @@PONY_DATA_MAPPING[key]
			continue unless map
			mapData = @@PONY_DATA[map]
			if mapData.enum and type(value) == 'string'
				@dataTable[key] = mapData.fix(mapData.enumMappingBackward[value\upper()])
			else
				@dataTable[key] = mapData.fix(value)
	ValueChanges: (key, oldVal, newVal, saveNow = @exists and @saveOnChange) =>
		if @nwObj and @updateNWObject
			{:getFunc} = @@PONY_DATA[key]
			@nwObj["Set#{getFunc}"](@nwObj, newVal, @networkNWObject)
		@Save() if saveNow
	SetFilename: (filename) =>
		@filename = filename
		@filenameFull = "#{filename}.txt"
		@fpath = "#{@@DATA_DIR}#{filename}.txt"
		@preview = "#{@@DATA_DIR}thumbnails/#{filename}.png"
		@fpathBackup = "#{@@DATA_DIR}#{filename}.bak.txt"
		@fpathFull = "data/#{@@DATA_DIR}#{filename}.txt"
		@isOpen = @filename ~= nil
		@exists = file.Exists(@fpath, 'DATA')
		return @exists
	SetNetworkData: (nwObj) => @nwObj = nwObj
	SetPonyData: (nwObj) => @nwObj = nwObj
	SetPonyDataController: (nwObj) => @nwObj = nwObj
	SetPonyController: (nwObj) => @nwObj = nwObj
	SetController: (nwObj) => @nwObj = nwObj
	SetDataController: (nwObj) => @nwObj = nwObj

	SetNetworkOnChange: (newVal = true) => @networkNWObject = newVal
	SetUpdateOnChange: (newVal = true) => @updateNWObject = newVal

	GetNetworkOnChange: => @networkNWObject
	GetUpdateOnChange: => @updateNWObject

	GetNetworkData: => @nwObj
	GetPonyData: => @nwObj
	GetPonyDataController: => @nwObj
	GetPonyController: => @nwObj
	GetController: => @nwObj
	GetDataController: => @nwObj

	IsValid: => @valid
	Exists: => @exists
	FileExists: => @exists
	IsExists: => @exists
	GetFileName: => @filename
	GetFilename: => @filename
	GetFileNameFull: => @filenameFull
	GetFilenameFull: => @filenameFull
	GetFilePath: => @fpath
	GetFullFilePath: => @fpathFull
	SerealizeValue: (valID = '') =>
		map = @@PONY_DATA[valID]
		return unless map
		val = @dataTable[valID]
		if map.enum
			return map.enumMapping[val] or map.enumMapping[map.default()]
		elseif map.serealize
			return map.serealize(val)
		else
			return val
	Serealize: (prettyPrint = true) =>
		serTab = {key, @SerealizeValue(key) for key, val in pairs @dataTable}
		util.TableToJSON(serTab, prettyPrint)
	GetAsNetworked: => {getFunc, @dataTable[k] for k, {:getFunc} in pairs @@PONY_DATA}

	@READ_SUCCESS = 0
	@ERR_FILE_NOT_EXISTS = 1
	@ERR_FILE_EMPTY = 2
	@ERR_FILE_CORRUPT = 3
	ReadFromDisk: (force = false, doBackup = true) =>
		return @@ERR_FILE_NOT_EXISTS unless @exists
		fRead = file.Read(@fpath, 'DATA')
		return @@ERR_FILE_EMPTY if not fRead or fRead == ''
		readTable = util.JSONToTable(fRead)
		return @@ERR_FILE_CORRUPT unless readTable
		return @SetupData(readTable, force, doBackup) or @@READ_SUCCESS

	SaveAs: (path = @fpath) =>
		fCreate = @Serealize()
		file.Write(path, fCreate)

	SavePreview: (path = @preview) =>
		buildingModel = ClientsideModel('models/ppm/ppm2_stage.mdl', RENDERGROUP_OTHER)
		buildingModel\SetNoDraw(true)
		buildingModel\SetModelScale(0.9)
		model = ClientsideModel('models/ppm/player_default_base_new_nj.mdl')

		with model
			\SetNoDraw(true)
			data = @CreateCustomController(model)
			.__PPM2_PonyData = data
			ctrl = data\GetRenderController()

			if bg = data\GetBodygroupController()
				bg\ApplyBodygroups()

			with model\PPMBonesModifier()
				\ResetBones()
				hook.Call('PPM2.SetupBones', nil, model, data)
				\Think(true)

			\SetSequence(22)
			\FrameAdvance(0)

		timer.Simple 0.5, ->
			renderTarget = GetRenderTarget('ppm2_save_preview_generate2', 1024, 1024, false)
			renderTarget\Download()
			render.PushRenderTarget(renderTarget)
			--render.SuppressEngineLighting(true)
			render.Clear(0, 0, 0, 255, true, true)
			cam.Start3D(Vector(49.373046875, -35.021484375, 58.332901000977), Angle(0, 141, 0), 90, 0, 0, 1024, 1024)

			buildingModel\DrawModel()

			with model
				data = .__PPM2_PonyData
				ctrl = data\GetRenderController()

				if bg = data\GetBodygroupController()
					bg\ApplyBodygroups()

				with model\PPMBonesModifier()
					\ResetBones()
					hook.Call('PPM2.SetupBones', nil, model, data)
					\Think(true)

				with ctrl
					\DrawModels()
					\HideModels(true)
					\PreDraw(model, true)

				\DrawModel()
				ctrl\PostDraw(model, true)

			cam.End3D()

			data = render.Capture({
				format: 'png'
				x: 0
				y: 0
				w: 1024
				h: 1024
				alpha: false
			})

			model\Remove()
			buildingModel\Remove()

			file.Write(path, data)

			--render.SuppressEngineLighting(false)
			render.PopRenderTarget()

	Save: (doBackup = true, preview = true) =>
		if doBackup and @exists
			fRead = file.Read(@fpath, 'DATA')
			file.Write(@fpathBackup, fRead)
		@SaveAs(@fpath)
		@SavePreview(@preview) if preview
		@exists = true

PPM2.PonyDataInstance = PonyDataInstance

PPM2.MainDataInstance = nil
PPM2.GetMainData = ->
	if not PPM2.MainDataInstance
		PPM2.MainDataInstance = PonyDataInstance('_current', nil, true, true)
		if not PPM2.MainDataInstance\FileExists()
			PPM2.MainDataInstance\Save()
	return PPM2.MainDataInstance


plugins/base: Add PRIVMSG sender

plugins/base: Add PRIVMSG sender

cqueues = require 'cqueues'
import IRCClient from require 'irc'

IRCClient\add_handler '001', =>
	if @config.autojoin
		for channel in *@config.autojoin
			@send_raw ("JOIN %s")\format channel

IRCClient\add_handler 'PING', (sender, args, last)=>
	@send_raw ("PONG :%s")\format last

IRCClient\add_handler 'ERROR', =>
	time = os.time()
	if time > @data.last_connect + 30
		@connect!
	else
		cqueues.sleep(@data.last_connect + 30 - time)

IRCClient\add_handler '433', =>
	@data.nick_test = 0 if not @data.nick_test
	@data.nick_test += 1
	cqueues.sleep 0.5
	if @data.nick_test >= 30
		@disconnect!
	else
		@send_raw ('NICK %s[%d]')\format @config.nick, @data.nick_test

IRCClient\add_sender 'PRIVMSG', (channel, message)=>
	for line in message\gmatch("[^\r\n]+")
		@send_raw "PRIVMSG #{channel} :#{line}"
		unless @server.ircv3_caps["echo-message"]
			@process ":#{@config.nick}!local@localhost PRIVMSG #{channel} :(local) #{line}"


remove extra "Your submissions" header

remove extra "Your submissions" header

import Widget from require "lapis.html"

quizs = require('quiz.groups')
kr = require('quiz.kr')

model = require('model')
msk_time = require "msk_time"
config = require("lapis.config").get!

Helpers = require('views.helpers')

class AllTests extends Widget
  @include Helpers

  content: =>
    div class: "body", ->
      if @prep
        a href: @url_for("prep-submissions"), ->
          text @_("code submissions")
        text ' | '
        a href: @url_for("prep-quizs"), ->
          text @_("finished quizes")
        text ' | '
        a href: @url_for("prep-quizs-today"), ->
          text @_("today quizes")
        text ' | '
        a href: @url_for("quiz-state"), ->
          text @_("enable/disable quiz")
        for i, name in ipairs kr
          text ' | '
          a href: @url_for("prep-kr", {name: name}), ->
            text @_("kr") .. i
      if config.checker_url
        p @_("Submit programming assignment")
        @submission_form!
      p @_("Start new quiz:")
      for _, group in ipairs(quizs)
        group_name = group[1]
        group_quizs = group[2]
        element 'table', -> tr ->
          td ->
            text group_name
          for _, quiz in ipairs(group_quizs)
            quiz_name = quiz[1]
            if model.Quiz.can_create(quiz_name) or @prep
              td ->
                @new_test_button quiz_name
      my_quizs = (t, state) ->
        qq = model.my_quizs(@, state)
        if #qq > 0
          p t
        element 'ul', ->
          for quiz in *qq
            element 'li', ->
              time = msk_time(quiz.created_at)
              raw quiz\anchor(@) .. ', ' .. time
              if quiz.state == model.FINISHED
                r = quiz.right_answers
                a = quiz.tasks
                text " | "
                b style: 'color: ' .. quiz\color!, ->
                  text "#{r} / #{a}"
      my_quizs @_("Your active quizes:"), model.ACTIVE
      my_quizs @_("Your finished quizes:"), model.FINISHED
      submissions = model.Submission\select(
        'where submission.user = ? order by id', @session.user)
      @print_submissions(submissions)

import Widget from require "lapis.html"

quizs = require('quiz.groups')
kr = require('quiz.kr')

model = require('model')
msk_time = require "msk_time"
config = require("lapis.config").get!

Helpers = require('views.helpers')

class AllTests extends Widget
  @include Helpers

  content: =>
    div class: "body", ->
      if @prep
        a href: @url_for("prep-submissions"), ->
          text @_("code submissions")
        text ' | '
        a href: @url_for("prep-quizs"), ->
          text @_("finished quizes")
        text ' | '
        a href: @url_for("prep-quizs-today"), ->
          text @_("today quizes")
        text ' | '
        a href: @url_for("quiz-state"), ->
          text @_("enable/disable quiz")
        for i, name in ipairs kr
          text ' | '
          a href: @url_for("prep-kr", {name: name}), ->
            text @_("kr") .. i
      if config.checker_url
        p @_("Submit programming assignment")
        @submission_form!
      p @_("Start new quiz:")
      for _, group in ipairs(quizs)
        group_name = group[1]
        group_quizs = group[2]
        element 'table', -> tr ->
          td ->
            text group_name
          for _, quiz in ipairs(group_quizs)
            quiz_name = quiz[1]
            if model.Quiz.can_create(quiz_name) or @prep
              td ->
                @new_test_button quiz_name
      my_quizs = (t, state) ->
        qq = model.my_quizs(@, state)
        if #qq > 0
          p t
        element 'ul', ->
          for quiz in *qq
            element 'li', ->
              time = msk_time(quiz.created_at)
              raw quiz\anchor(@) .. ', ' .. time
              if quiz.state == model.FINISHED
                r = quiz.right_answers
                a = quiz.tasks
                text " | "
                b style: 'color: ' .. quiz\color!, ->
                  text "#{r} / #{a}"
      my_quizs @_("Your active quizes:"), model.ACTIVE
      my_quizs @_("Your finished quizes:"), model.FINISHED
      h1 @_[[Your submissions]]
      submissions = model.Submission\select(
        'where submission.user = ? order by id', @session.user)
      @print_submissions(submissions)


github: limit commit output to 3 commits

Former-commit-id: 3b0f8452f38b32fd13e5e255058a360a5a11e1c1 [formerly e6b3b328c08acf043fb31c4d32223607bd32f2eb]
Former-commit-id: c833e34faf0dd18c27d4f633c619b1bd6268c547

util = require 'util'

ivar2.webserver.regUrl '/github/(.*)', (req, res) ->
  destination = req.url\match('/github/(.+)/?$')
  destination = destination\gsub '%%23', '#'
  json = util.json.decode(req.body)

  repo = json.repository.full_name
  branch = json.ref\gsub 'refs/heads/', ''

  for i, c in pairs(json.commits)
    if i > 3
      break
    ivar2\Msg 'privmsg', destination, nil, "#{repo}: #{branch}, #{c.author.username}: #{c.message} #{c.url}"
  if #json.commits > 3
    ivar2\Msg 'privmsg', destination, nil, "#{repo}: #{branch}, #{#json.commits-3} more commits not displayed."

  res\set_status(200)
  res\set_header('Content-Type', 'text/plain')
  res\set_body('ok')
  res\send()

return {}

util = require 'util'

ivar2.webserver.regUrl '/github/(.*)', (req, res) ->
  destination = req.url\match('/github/(.+)/?$')
  destination = destination\gsub '%%23', '#'
  json = util.json.decode(req.body)

  repo = json.repository.full_name
  branch = json.ref\gsub 'refs/heads/', ''

  for c in *json.commits
    ivar2\Msg 'privmsg', destination, nil, "#{repo}: #{branch}, #{c.author.username}: #{c.message} #{c.url}"

  res\set_status(200)
  res\set_header('Content-Type', 'text/plain')
  res\set_body('ok')
  res\send()

return {}


Added all entities to the ECS! Something you actually can't do with broken lovetoys!


Rename shared_either() to behaves_like_either() in either_spec

require 'moon.all'
import mtype from require 'ngx-oauth.util'
import Left, Right, either, encase, encase2 from require 'ngx-oauth.either'

behaves_like_either = (type_func, type_name) ->
  other = type_func == Right and Left or Right

  describe 'metatable.__eq', ->
    context 'same Either types with same value', ->
      it 'returns true', -> assert.is_true type_func(42) == type_func(42)

    context 'same Either types with different value', ->
      it 'returns false', -> assert.is_false type_func(42) == type_func(66)

    context 'different Either types with same value', ->
      it 'returns false', -> assert.is_false type_func(42) == other(42)

    context 'Either with non-Either', ->
      it 'returns false', -> assert.is_false type_func(66) == 66

  describe 'metatable.__tostring', ->
    it "returns #{type_name}(<value>)", ->
      assert.same "#{type_name}(42)", tostring(type_func(42))

  describe 'metatable.__type', ->
    it "is #{type_name}", ->
      assert.same type_name, getmetatable(type_func(42)).__type

describe 'Left', ->
  behaves_like_either Left, 'Left'

  left = Left(66)

  for func_name in *{'ap', 'map', 'chain'} do
    describe func_name, ->
      it 'returns self', ->
        assert.equal left, left[func_name](Right(42))

describe 'Right', ->
  behaves_like_either Right, 'Right'

  right = Right(42)

  describe 'ap', ->
    fright = Right((x) -> x * 2)

    context 'given Right', ->
      it "returns Right which value is result of applying this Right's value to given Right's value", ->
        given = Right(42)
        result = fright.ap(given)
        assert.equal Right(84), result

    context 'given Left', ->
      it 'returns given Left', ->
        given = Left(66)
        assert.equal given, fright.ap(given)

    context 'neither Left, nor Right', ->
      it 'throws error', ->
        assert.has_error -> fright.ap(66)

    context 'when this value is not a function', ->
      it 'throws error', ->
        assert.has_error -> right.ap(Left(66))

  describe 'map', ->
    it "returns Right which value is result of applying given function to this Right's value", ->
      result = right.map((x) -> x * 2)
      assert.equal Right(84), result

  describe 'chain', ->
    it "returns result of applying given function to this Right's value", ->
      assert.same 84, right.chain((x) -> x * 2)

describe 'either', ->

  before_each ->
    export onleft = mock(->)
    export onright = mock(->)

  context 'given Left', ->
    it "calls onleft handler with Left's value", ->
      either(onleft, onright, Left(66))
      assert.stub(onleft).called_with(66)
      assert.stub(onright).not_called()

  context 'given Right', ->
    it "calls onright handler with Right's value", ->
      either(onleft, onright, Right(42))
      assert.stub(onleft).not_called()
      assert.stub(onright).called_with(42)

  context 'given neither Left, nor Right', ->
    it 'throws error and does not call any handler', ->
      assert.has_error -> either(->, ->, {})
      assert.stub(onleft).not_called()
      assert.stub(onright).not_called()

shared_encase = (encase_func) ->
  it 'returns a function that wraps the given func and passes its arguments to it', ->
    func = mock(->)
    func2 = encase_func(func)
    assert.is_function func2
    func2(1, 2, 3)
    assert.stub(func).called_with(1, 2, 3)

describe 'encase', ->

  shared_encase encase

  context 'when given func has not raised error', ->
    it "nested function returns Right with the func's return value", ->
      result = encase(-> 'hai!')()
      assert.equal Right('hai!'), result

  context 'when given func has raised error', ->
    it 'nested function returns Left with an error message', ->
      result = encase(table.insert)()
      assert.same 'Left', mtype(result)
      assert.match 'bad argument.*', result.value

describe 'encase2', ->

  shared_encase encase2

  context 'when given func returned non-nil value', ->
    func = -> 'OK!', nil

    it "nested function returns Right with the func's 1st result value", ->
      result = encase2(func)()
      assert.equal Right('OK!'), result

  context 'when func returns nil and a value', ->
    func = -> nil, 'FAIL!'

    it "nested function returns Left with the func's 2nd result value", ->
      result = encase2(func)()
      assert.equal Left('FAIL!'), result

require 'moon.all'
import mtype from require 'ngx-oauth.util'
import Left, Right, either, encase, encase2 from require 'ngx-oauth.either'

shared_either = (type_func, type_name) ->
  other = type_func == Right and Left or Right

  describe 'metatable.__eq', ->
    context 'same Either types with same value', ->
      it 'returns true', -> assert.is_true type_func(42) == type_func(42)

    context 'same Either types with different value', ->
      it 'returns false', -> assert.is_false type_func(42) == type_func(66)

    context 'different Either types with same value', ->
      it 'returns false', -> assert.is_false type_func(42) == other(42)

    context 'Either with non-Either', ->
      it 'returns false', -> assert.is_false type_func(66) == 66

  describe 'metatable.__tostring', ->
    it "returns #{type_name}(<value>)", ->
      assert.same "#{type_name}(42)", tostring(type_func(42))

  describe 'metatable.__type', ->
    it "is #{type_name}", ->
      assert.same type_name, getmetatable(type_func(42)).__type

describe 'Left', ->
  left = Left(66)

  for func_name in *{'ap', 'map', 'chain'} do
    describe func_name, ->
      it 'returns self', ->
        assert.equal left, left[func_name](Right(42))

  shared_either Left, 'Left'

describe 'Right', ->
  right = Right(42)

  describe 'ap', ->
    fright = Right((x) -> x * 2)

    context 'given Right', ->
      it "returns Right which value is result of applying this Right's value to given Right's value", ->
        given = Right(42)
        result = fright.ap(given)
        assert.equal Right(84), result

    context 'given Left', ->
      it 'returns given Left', ->
        given = Left(66)
        assert.equal given, fright.ap(given)

    context 'neither Left, nor Right', ->
      it 'throws error', ->
        assert.has_error -> fright.ap(66)

    context 'when this value is not a function', ->
      it 'throws error', ->
        assert.has_error -> right.ap(Left(66))

  describe 'map', ->
    it "returns Right which value is result of applying given function to this Right's value", ->
      result = right.map((x) -> x * 2)
      assert.equal Right(84), result

  describe 'chain', ->
    it "returns result of applying given function to this Right's value", ->
      assert.same 84, right.chain((x) -> x * 2)

  shared_either Right, 'Right'

describe 'either', ->

  before_each ->
    export onleft = mock(->)
    export onright = mock(->)

  context 'given Left', ->
    it "calls onleft handler with Left's value", ->
      either(onleft, onright, Left(66))
      assert.stub(onleft).called_with(66)
      assert.stub(onright).not_called()

  context 'given Right', ->
    it "calls onright handler with Right's value", ->
      either(onleft, onright, Right(42))
      assert.stub(onleft).not_called()
      assert.stub(onright).called_with(42)

  context 'given neither Left, nor Right', ->
    it 'throws error and does not call any handler', ->
      assert.has_error -> either(->, ->, {})
      assert.stub(onleft).not_called()
      assert.stub(onright).not_called()

shared_encase = (encase_func) ->
  it 'returns a function that wraps the given func and passes its arguments to it', ->
    func = mock(->)
    func2 = encase_func(func)
    assert.is_function func2
    func2(1, 2, 3)
    assert.stub(func).called_with(1, 2, 3)

describe 'encase', ->

  shared_encase encase

  context 'when given func has not raised error', ->
    it "nested function returns Right with the func's return value", ->
      result = encase(-> 'hai!')()
      assert.equal Right('hai!'), result

  context 'when given func has raised error', ->
    it 'nested function returns Left with an error message', ->
      result = encase(table.insert)()
      assert.same 'Left', mtype(result)
      assert.match 'bad argument.*', result.value

describe 'encase2', ->

  shared_encase encase2

  context 'when given func returned non-nil value', ->
    func = -> 'OK!', nil

    it "nested function returns Right with the func's 1st result value", ->
      result = encase2(func)()
      assert.equal Right('OK!'), result

  context 'when func returns nil and a value', ->
    func = -> nil, 'FAIL!'

    it "nested function returns Left with the func's 2nd result value", ->
      result = encase2(func)()
      assert.equal Left('FAIL!'), result


alot: Prevent false positives.

alot: Prevent false positives.

Former-commit-id: c638dfd73f66b19c358733dbf572ef06671b2b4a [formerly 19c6131f4b1a7bb5ade264cc279e6fa6307f381f]
Former-commit-id: 950a816d9f73e3591cc0133f2cf593af73002d7f

alot = (source, destination) =>
  say 'http://hyperboleandahalf.blogspot.no/2010/04/alot-is-better-than-you-at-everything.html'
PRIVMSG:
  '%f[%a][aA][lL][oO][tT]%f[%A]': alot

alot = (source, destination) =>
  say 'http://hyperboleandahalf.blogspot.no/2010/04/alot-is-better-than-you-at-everything.html'
PRIVMSG:
  '.*[aA][lL][oO][tT].*': alot


list: prettier output, friendly error message

Prettyfi = (patt) ->
  -- Make them prettier
  patt = patt\gsub('%^%%p', '!')
  patt = patt\gsub('%$$', '')
  patt = patt\gsub('^%^', '')
  patt = patt\gsub '%$', ''
  return patt

listModules = (source, destination, arg) =>
  out = {}
  for moduleName, moduleTable in next, @Events!['PRIVMSG']
    unless @IsModuleDisabled moduleName, destination
      table.insert out, moduleName
  if #out > 0
    say "Modules: %s", table.concat(out, ' ')

listPatterns = (source, destination, moduleName) =>
  moduleTable = @Events!['PRIVMSG'][moduleName]
  unless moduleTable
    return

  if @IsModuleDisabled moduleName, destination
    return

  out = {}
  for pattern, callback in next, moduleTable
    patt = @ChannelCommandPattern(pattern, moduleName, destination)
    patt = Prettyfi patt
    table.insert out, patt

  if #out > 0
    say "%s patterns:\n %s", moduleName, table.concat(out, ',\n ')

Apropos = (s, d, what) =>
  moduleTable = @Events!['PRIVMSG']
  unless moduleTable
    return

  out = {}
  for moduleName, moduleTable in next, moduleTable
    if @IsModuleDisabled moduleName, destination
      return

    for pattern, callback in next, moduleTable
      if type(pattern) == 'string'
        patt = @ChannelCommandPattern(pattern, moduleName, destination)
        if patt and type(patt) == 'string' and patt\match(what)
          out[#out+1] = "#{ivar2.util.bold moduleName} module has pattern #{ivar2.util.italic Prettyfi patt}"
  if #out == 0
    say "Could not find a command pattern that matches that."
  else
    say table.concat(out, ',\n')

PRIVMSG:
  '^%plist$': listModules
  '^%plist (%w+)$': listPatterns
  '^%phelp$': listModules
  '^%phelp (%w+)$': listPatterns
  '^%papropos (.*)$': Apropos

listModules = (source, destination, arg) =>
  out = {}
  for moduleName, moduleTable in next, @Events!['PRIVMSG']
    unless @IsModuleDisabled moduleName, destination
      table.insert out, moduleName
  if #out > 0
    say "Modules: %s", table.concat(out, ' ')

listPatterns = (source, destination, moduleName) =>
  moduleTable = @Events!['PRIVMSG'][moduleName]
  unless moduleTable
    return

  if @IsModuleDisabled moduleName, destination
    return

  out = {}
  for pattern, callback in next, moduleTable
    patt = @ChannelCommandPattern(pattern, moduleName, destination)
    -- Make them prettier
    patt = patt\gsub('%^%%p', '!')
    patt = patt\gsub('%$$', '')
    patt = patt\gsub('^%^', '')
    table.insert out, patt

  if #out > 0
    say "%s patterns:\n %s", moduleName, table.concat(out, ',\n ')

Apropos = (s, d, what) =>
  moduleTable = @Events!['PRIVMSG']
  unless moduleTable
    return

  out = {}
  for moduleName, moduleTable in next, moduleTable
    if @IsModuleDisabled moduleName, destination
      return

    for pattern, callback in next, moduleTable
      if type(pattern) == 'string'
        patt = @ChannelCommandPattern(pattern, moduleName, destination)
        if patt and type(patt) == 'string' and patt\match what
          out[#out+1] = "#{ivar2.util.bold moduleName} module has pattern #{ivar2.util.italic patt}"
  say table.concat(out, ',\n')

PRIVMSG:
  '^%plist$': listModules
  '^%plist (%w+)$': listPatterns
  '^%phelp$': listModules
  '^%phelp (%w+)$': listPatterns
  '^%papropos (.*)$': Apropos


plugins/sasl: Remove debug print

plugins/sasl: Remove debug print

import to_base64 from require 'basexx'
import IRCClient from require 'irc'

handlers =
	PLAIN: =>
		c = coroutine.create (auth)=>
			prefix, command, args, trail = coroutine.yield "AUTHENTICATE PLAIN"
			assert args[#args] == "+", "Unable to continue: #{table.concat(args)}"
			payload = ""
			payload ..= "#{auth.identity or auth.username}\000"
			payload ..= "#{auth.username}\000#{auth.password}"
			payload = "AUTHENTICATE #{to_base64 payload}"
			prefix, command, args, trail = coroutine.yield payload
			assert command == "903" or command == "900", trail

			-- destroy self on finish
			@fire_hook 'CAP_ACK' -- finished SASL, release lock on CAP END
			@sasl_handler = nil

		@send_raw select(2, assert(coroutine.resume c, self, @config.auth))
		return c

IRCClient\add_hook 'CAP_ACK.sasl', (values = 'PLAIN')=>
	return unless @config.auth
	methods = [w for w in values\gmatch "[^,]+"]
	for method in *methods
		if handlers[method] and @config.auth.mechanism == method
			-- found usable method
			@set_caps += 1
			@sasl_handler = handlers[method] self, @config.auth

for command in *{"AUTHENTICATE", "900", "901", "903", "904", "905", "906"}
	IRCClient\add_handler command, (prefix, args, trail)=>
		return if not @sasl_handler
		ok, result = coroutine.resume @sasl_handler, prefix, command, args, trail
		if not ok
			@sasl_handler = nil
			@fire_hook 'CAP_ACK' -- failed SASL, release lock on CAP END
			error "Error in SASL mechanism: #{result}"
		else
			@send_raw result

import to_base64 from require 'basexx'
import IRCClient from require 'irc'

handlers =
	PLAIN: =>
		c = coroutine.create (auth)=>
			prefix, command, args, trail = coroutine.yield "AUTHENTICATE PLAIN"
			assert args[#args] == "+", "Unable to continue: #{table.concat(args)}"
			payload = ""
			payload ..= "#{auth.identity or auth.username}\000"
			payload ..= "#{auth.username}\000#{auth.password}"
			payload = "AUTHENTICATE #{to_base64 payload}"
			prefix, command, args, trail = coroutine.yield payload
			print command
			assert command == "903" or command == "900", trail

			-- destroy self on finish
			@fire_hook 'CAP_ACK' -- finished SASL, release lock on CAP END
			@sasl_handler = nil

		@send_raw select(2, assert(coroutine.resume c, self, @config.auth))
		return c

IRCClient\add_hook 'CAP_ACK.sasl', (values = 'PLAIN')=>
	return unless @config.auth
	methods = [w for w in values\gmatch "[^,]+"]
	for method in *methods
		if handlers[method] and @config.auth.mechanism == method
			-- found usable method
			@set_caps += 1
			@sasl_handler = handlers[method] self, @config.auth

for command in *{"AUTHENTICATE", "900", "901", "903", "904", "905", "906"}
	IRCClient\add_handler command, (prefix, args, trail)=>
		return if not @sasl_handler
		ok, result = coroutine.resume @sasl_handler, prefix, command, args, trail
		if not ok
			@sasl_handler = nil
			@fire_hook 'CAP_ACK' -- failed SASL, release lock on CAP END
			error "Error in SASL mechanism: #{result}"
		else
			@send_raw result


Implement utils.Input.define

Implement utils.Input.define

export ^

class input
	__defines = {}

	enable: ->

	-- Allows the user to define custom key(s) to check for by name
	define: (key, value) ->
		__defines[key] = value

	-- key is either a string (from Key or the Löve KeyConstants) or a table of strings
	check: (key) ->
		return false if key == nil

		-- Consult the user defined "keys" first
		if type string
			for k, v in pairs __defines
				if k == key -- Found a match, so assign key the value
					key = v
					break

		switch type key
			when "string"
				love.keyboard.isDown key
			when "table"
				love.keyboard.isDown unpack key
			else
				print "Unknown key type", type key

Input = input!

export ^

class input
	enable: ->
	define: (value) ->

	-- key is either a string (from Key or the Löve KeyConstants) or a table of strings
	check: (key) ->
		return false if key == nil
		switch type key
			when "string"
				love.keyboard.isDown key
			when "table"
				love.keyboard.isDown unpack key
			else
				print "Unknown key type", type key

Input = input!


Add secret

Add secret

config = require 'lapis.config'
secrets = require 'secrets'

config {'development', 'development-perftest'}, ->
    mysql ->
        host '127.0.0.1'
        user 'csweek'
        password 'csweek'
        database 'csweek'

    smtp_server 'mail.csnedelja.mg.edu.rs'
    smtp_port 587
    smtp_username secrets.mail_user
    smtp_password secrets.mail_password
    smtp_from 'MG Nedelja informatike - prijave <prijave@csnedelja.mg.edu.rs>'

    port 8080
    num_workers 1
    code_cache 'off'
    pid_file 'nginx.pid'
    log_file 'logs/error.log'
    log_level 'debug'
    listen_address '127.0.0.1'
    secret 'this is not so secret!'
    content_prefix '../content/'
    filesize_limit 6 * 15 * 1024 * 1024 + 1024
    single_file_limit 15 * 1024 * 1024

    email_cooldown 20
    uploads_dir 'uploads'

config {'production', 'production-perftest' }, ->
    mysql ->
        host '127.0.0.1'
        user secrets.mysql_user
        password secrets.mysql_password
        database secrets.mysql_db

    smtp_server 'mail.csnedelja.mg.edu.rs'
    smtp_port 587
    smtp_username secrets.mail_user
    smtp_password secrets.mail_password

    port 8989
    num_workers 1
    code_cache 'on'
    pid_file '/var/run/nginx.pid'
    log_file '/var/log/nginx/error.log'
    log_level 'warn'
    listen_address '127.0.0.1'
    secret secrets.app_secret
    content_prefix 'content/'
    filesize_limit 6 * 15 * 1024 * 1024 + 1024
    single_file_limit 15 * 1024 * 1024

    email_cooldown 30 * 60
    uploads_dir 'uploads'

config {'development-perftest', 'production-perftest'}, ->
    code_cache 'on'
    measure_performance -> true

config = require 'lapis.config'
secrets = require 'secrets'

config {'development', 'development-perftest'}, ->
    mysql ->
        host '127.0.0.1'
        user 'csweek'
        password 'csweek'
        database 'csweek'

    smtp_server 'mail.csnedelja.mg.edu.rs'
    smtp_port 587
    smtp_username secrets.mail_user
    smtp_password secrets.mail_password
    smtp_from 'MG Nedelja informatike - prijave <prijave@csnedelja.mg.edu.rs>'

    port 8080
    num_workers 1
    code_cache 'off'
    pid_file 'nginx.pid'
    log_file 'logs/error.log'
    log_level 'debug'
    listen_address '127.0.0.1'
    secret 'this is not so secret!'
    content_prefix '../content/'
    filesize_limit 6 * 15 * 1024 * 1024 + 1024
    single_file_limit 15 * 1024 * 1024

    email_cooldown 20
    uploads_dir 'uploads'

config {'production', 'production-perftest' }, ->
    mysql ->
        host '127.0.0.1'
        user secrets.mysql_user
        password secrets.mysql_password
        database secrets.mysql_db

    smtp_server 'mail.csnedelja.mg.edu.rs'
    smtp_port 587
    smtp_username secrets.mail_user
    smtp_password secrets.mail_password

    port 8989
    num_workers 1
    code_cache 'on'
    pid_file '/var/run/nginx.pid'
    log_file '/var/log/nginx/error.log'
    log_level 'warn'
    listen_address '127.0.0.1'
    content_prefix 'content/'
    filesize_limit 6 * 15 * 1024 * 1024 + 1024
    single_file_limit 15 * 1024 * 1024

    email_cooldown 30 * 60
    uploads_dir 'uploads'

config {'development-perftest', 'production-perftest'}, ->
    code_cache 'on'
    measure_performance -> true


clue: fluffle

clue: fluffle

Former-commit-id: 601cb5c14ad20d1a8bbaab10c1eb9ea610efe2c2
Former-commit-id: 1550e6a9db85b351095b07613e8887ea0dd9a64e

-- Clue module requires clue data files and tosql.py from emnh/clue2

dbi = require 'DBI'
require'logging.console'
log = logging.console()
conn = false

attrs = {'grammar','reference', 'country', 'context', 'text'}

connect = ->
  conn, err = DBI.Connect('PostgreSQL', ivar2.config.dbname, ivar2.config.dbuser, ivar2.config.dbpass, ivar2.config.dbhost, ivar2.config.dbport)
  unless conn
    log\error "Unable to connect to DB: #{err}"
    return

  conn\autocommit(true)

dbh = ->
  connect! unless conn

  -- Check if connection is alive
  alive = conn\ping!
  connect! unless alive

  success, err = DBI.Do(conn, 'SELECT NOW()')
  unless success
    log\error "SQL Connection :#{err}"
    connect!

  return conn

lookup = (source, destination, lookup) =>
  stmt = dbh!\prepare [[
    SELECT
      tablename
    from
      pg_catalog.pg_tables
    where
      tablename like 'cl%'
    ]]
  stmt\execute!
  tables = {}
  for row in stmt\rows(true) -- true for column names
    tables[#tables+1] = row.tablename

  out = {}
  for dict in *tables
    out[dict] = {}
    stmt = dbh!\prepare [[
      SELECT
        *
      FROM
        ]]..dict..[[
      WHERE
        word = ?
      ]]
    stmt\execute lookup
    for row in stmt\rows(true) -- true for column names
      table.insert(out[dict], row)

  i = 0
  for dict, rows in pairs out
    res = {}
    dfrom = dict\sub(3, 4)
    to = dict\sub(5, 6)
    first = true
    for row in *rows
      i += 1
      if first
        table.insert(res, "[#{ivar2.util.bold row.word}]")
        table.insert(res, "(#{ivar2.util.italic dfrom}")
        table.insert(res, "-> #{ivar2.util.italic to})")
        first = false
      for attr in *attrs
        if row[attr] != nil and row[attr] != ''
          if attr == 'grammar'
            table.insert(res, "(#{row[attr]}. )")
          else
            table.insert(res, row[attr])
    say table.concat(res, ' ')
  if i == 0
    reply 'Nope.'

upper = (s) ->
  (string.upper(s)\gsub('æ','Æ')\gsub('ø','Ø')\gsub('å','Å'))

getWord = (lang, wordclass, count, maxlen, startswith) ->
  unless startswith
    startswith = ''
  else
    startswith = "AND left(word,1) = '#{startswith}'"
  sql = [[
    SELECT
     *
    FROM
      ]]..lang..[[
    WHERE
      grammar = ?
    AND
      length(word) < ?
    ]]..startswith..[[
    ORDER BY
      random()
    LIMIT ?
  ]]
  stmt = dbh!\prepare sql
  stmt\execute wordclass, maxlen, count
  out = {}
  for row in stmt\rows(true) -- true for column names
    table.insert(out, row)
  return out

bankid = (source, destination) =>

  adj = getWord 'clnono', 'adj', 1, 8
  sub = getWord 'clnono', 's', 1, 10

  say "#{upper adj[1].word} #{upper sub[1].word}"

isLogWord = (word) ->
  myconn = dbh!
  sql = [[
    SELECT
     count(*)
    FROM
      log
    WHERE
      message ~* '\y]]..word..[[\y'
  ]]
  stmt = myconn\prepare sql
  stmt\execute!
  count = 0
  row = stmt\fetch!
  count = row[1]
  return tonumber(count) > 0

bankid2 = (source, destination) =>

  adj = ""
  sub = ""
  while true
    adj = getWord 'clnono', 'adj', 1, 20
    adj = adj[1].word
    continue unless isLogWord(adj)
  while true
    sub = getWord 'clnono', 's', 1, 20
    sub = sub[1].word
    continue unless isLogWord(sub)

  say "#{upper adj} #{upper sub}"

bankidmany = (source, destination, count) =>
  count = math.min(count, 100)

  adj = getWord 'clnono', 'adj', count, 8
  sub = getWord 'clnono', 's', count, 10

  out = {}
  for i=1, count
    table.insert(out, "#{upper adj[i].word} #{upper sub[i].word}")
  say table.concat(out, ', ')

adjsub = (source, destination, count) =>
  count = tonumber count
  if not count
    count = 1
  count = math.min(count, 100)

  adj = getWord 'clukuk', 'adj', count, 8
  sub = getWord 'clukuk', 'n', count, 10

  out = {}
  for i=1, count
    table.insert(out, "#{adj[i].word} #{sub[i].word}")
  say table.concat(out, ', ')

PRIVMSG:
  '^%pclue (.+)$': lookup
  '^%pbankid$': bankid
  '^%pbankid (%d+)$': bankidmany
  '^%padjsub$': adjsub
  '^%padjsub (%d+)$': adjsub
  '^%p?fluffle (.+)$': (source, destination, arg) =>
    choice = math.random(1, 26)
    alliteration = ('abcdefghjijklmnopqrstuvxyz')\sub(choice, choice)
    adj = getWord 'clukuk', 'adj', 1, 8, alliteration
    sub = getWord 'clukuk', 'n', 1, 10, alliteration
    n = ''
    if alliteration\match '[aeiou]'
      n = 'n'
    say "#{ivar2.util.trim arg} got fluffled by a#{n} #{adj[1].word} #{sub[1].word}"
  --'^%pbankid2$': bankid2

-- Clue module requires clue data files and tosql.py from emnh/clue2

dbi = require 'DBI'
require'logging.console'
log = logging.console()
conn = false

attrs = {'grammar','reference', 'country', 'context', 'text'}

connect = ->
  conn, err = DBI.Connect('PostgreSQL', ivar2.config.dbname, ivar2.config.dbuser, ivar2.config.dbpass, ivar2.config.dbhost, ivar2.config.dbport)
  unless conn
    log\error "Unable to connect to DB: #{err}"
    return

  conn\autocommit(true)

dbh = ->
  connect! unless conn

  -- Check if connection is alive
  alive = conn\ping!
  connect! unless alive

  success, err = DBI.Do(conn, 'SELECT NOW()')
  unless success
    log\error "SQL Connection :#{err}"
    connect!

  return conn

lookup = (source, destination, lookup) =>
  stmt = dbh!\prepare [[
    SELECT
      tablename
    from
      pg_catalog.pg_tables
    where
      tablename like 'cl%'
    ]]
  stmt\execute!
  tables = {}
  for row in stmt\rows(true) -- true for column names
    tables[#tables+1] = row.tablename

  out = {}
  for dict in *tables
    out[dict] = {}
    stmt = dbh!\prepare [[
      SELECT
        *
      FROM
        ]]..dict..[[
      WHERE
        word = ?
      ]]
    stmt\execute lookup
    for row in stmt\rows(true) -- true for column names
      table.insert(out[dict], row)

  i = 0
  for dict, rows in pairs out
    res = {}
    dfrom = dict\sub(3, 4)
    to = dict\sub(5, 6)
    first = true
    for row in *rows
      i += 1
      if first
        table.insert(res, "[#{ivar2.util.bold row.word}]")
        table.insert(res, "(#{ivar2.util.italic dfrom}")
        table.insert(res, "-> #{ivar2.util.italic to})")
        first = false
      for attr in *attrs
        if row[attr] != nil and row[attr] != ''
          if attr == 'grammar'
            table.insert(res, "(#{row[attr]}. )")
          else
            table.insert(res, row[attr])
    say table.concat(res, ' ')
  if i == 0
    reply 'Nope.'

upper = (s) ->
  (string.upper(s)\gsub('æ','Æ')\gsub('ø','Ø')\gsub('å','Å'))

getWord = (lang, wordclass, count, maxlen) ->
  stmt = dbh!\prepare [[
    SELECT
     *
    FROM
      ]]..lang..[[
    WHERE
      grammar = ?
    AND
      length(word) < ?
    ORDER BY
      random()
    LIMIT ?
  ]]
  stmt\execute wordclass, maxlen, count
  out = {}
  for row in stmt\rows(true) -- true for column names
    table.insert(out, row)
  return out

bankid = (source, destination) =>

  adj = getWord 'clnono', 'adj', 1, 8
  sub = getWord 'clnono', 's', 1, 10

  say "#{upper adj[1].word} #{upper sub[1].word}"

isLogWord = (word) ->
  myconn = dbh!
  sql = [[
    SELECT
     count(*)
    FROM
      log
    WHERE
      message ~* '\y]]..word..[[\y'
  ]]
  stmt = myconn\prepare sql
  stmt\execute!
  count = 0
  row = stmt\fetch!
  count = row[1]
  return tonumber(count) > 0

bankid2 = (source, destination) =>

  adj = ""
  sub = ""
  while true
    adj = getWord 'clnono', 'adj', 1, 20
    adj = adj[1].word
    continue unless isLogWord(adj)
  while true
    sub = getWord 'clnono', 's', 1, 20
    sub = sub[1].word
    continue unless isLogWord(sub)

  say "#{upper adj} #{upper sub}"

bankidmany = (source, destination, count) =>
  count = math.min(count, 100)

  adj = getWord 'clnono', 'adj', count, 8
  sub = getWord 'clnono', 's', count, 10

  out = {}
  for i=1, count
    table.insert(out, "#{upper adj[i].word} #{upper sub[i].word}")
  say table.concat(out, ', ')

adjsub = (source, destination, count) =>
  count = tonumber count
  if not count
    count = 1
  count = math.min(count, 100)

  adj = getWord 'clukuk', 'adj', count, 8
  sub = getWord 'clukuk', 'n', count, 10

  out = {}
  for i=1, count
    table.insert(out, "#{adj[i].word} #{sub[i].word}")
  say table.concat(out, ', ')

PRIVMSG:
  '^%pclue (.+)$': lookup
  '^%pbankid$': bankid
  '^%pbankid (%d+)$': bankidmany
  '^%padjsub$': adjsub
  '^%padjsub (%d+)$': adjsub
  '^%pfluffle (.+)$': (source, destination, arg) =>
    adj = getWord 'clukuk', 'adj', 1, 8
    sub = getWord 'clukuk', 'n', 1, 10
    say "#{ivar2.util.trim arg} got fluffled by a #{adj[1].word} #{sub[1].word}"
  --'^%pbankid2$': bankid2


utf8: add upsidedown

utf8: add upsidedown

html2unicode = require'html'
math = require'math'

an = [[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789,.?!"'`()[]{}<>&_]]
ci = 'ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ⓪①②③④⑤⑥⑦⑧⑨'
bl = '𝔄𝔅ℭ𝔇𝔈𝔉𝔊ℌℑ𝔍𝔎𝔏𝔐𝔑𝔒𝔓𝔔ℜ𝔖𝔗𝔘𝔙𝔚𝔛𝔜ℨ𝔞𝔟𝔠𝔡𝔢𝔣𝔤𝔥𝔦𝔧𝔨𝔩𝔪𝔫𝔬𝔭𝔮𝔯𝔰𝔱𝔲𝔳𝔴𝔵𝔶𝔷'
ud = [[∀BƆDƎℲפHIſK˥WNOԀQRS┴∩ΛMX⅄Zɐqɔpǝɟƃɥᴉɾʞlɯuodbɹsʇnʌʍxʎz0ƖᄅƐㄣϛ9ㄥ86'˙¿¡,,,)(][}{><⅋‾]]

an2ci = {}
-- Circled letters are 3 bytes, just use sub string
for i=1, #an
  f = i*3+1-3
  t = i*3
  an2ci[an\sub(i,i)] = ci\sub(f, t)

an2bl = {}
i=1
-- Since blackletters have varying byte length, use the common lua pattern to find multibyte chars
for uchar in string.gfind(bl, "([%z\1-\127\194-\244][\128-\191]*)")
  an2bl[an\sub(i,i)] = uchar
  i = i +1

an2ud = {}
i=1
for uchar in string.gfind(ud, "([%z\1-\127\194-\244][\128-\191]*)")
  an2ud[an\sub(i,i)] = uchar
  i = i +1

codepoints = (str) ->
  str\gmatch("[%z\1-\127\194-\244][\128-\191]*")

unichr = (n) ->
  html2unicode('&#x%x;'\format(n))

wireplace = (offset, arg) ->
    s = arg or ''
    t = {}
    for i = 1, #s
      bc = string.byte(s, i, i)
      -- Replace space width ideographic space for fullwidth offset
      if bc == 32 and offset == 0xFEE0
        t[#t + 1] = '\227\128\128'
      elseif bc == 32
        t[#t + 1] = ' '
      elseif bc < 0x80 then
        t[#t + 1] = html2unicode("&#" .. (offset + bc) .. ";")
      else
        t[#t + 1] = s\sub(i, i)

    table.concat(t, "")

remap = (map, s) ->
  table.concat [map[s\sub(i,i)] or s\sub(i,i) for i=1, #s], ''

zalgo = (text, intensity=50) ->
  zalgo_chars = {}
  for i=0x0300, 0x036f
    zalgo_chars[i-0x2ff] = unichr(i)

  zalgo_chars[#zalgo_chars + 1] = unichr(0x0488)
  zalgo_chars[#zalgo_chars + 0] = unichr(0x0489)

  zalgoized = {}
  for letter in codepoints(text)
    zalgoized[#zalgoized + 1] = letter
    zalgo_num = math.random(1, intensity)
    for i=1, zalgo_num
      zalgoized[#zalgoized + 1] = zalgo_chars[math.random(1, #zalgo_chars)]
  table.concat(zalgoized)

PRIVMSG:
  '^%pwide (.+)$': (source, destination, arg) =>
    @Msg 'privmsg', destination, source, wireplace(0xFEE0, arg)
  '^%pblackletter (.+)$': (source, destination, arg) =>
    @Msg 'privmsg', destination, source, remap(an2bl, arg)
  '^%pcircled (.+)$': (source, destination, arg) =>
    @Msg 'privmsg', destination, source, remap(an2ci, arg)
  '^%pzalgo (.+)$': (source, destination, arg) =>
    @Msg 'privmsg', destination, source, zalgo(arg, 10)
  '^%pupsidedown (.+)$': (source, destination, arg) =>
    @Msg 'privmsg', destination, source, remap(an2ud, arg)

html2unicode = require'html'
math = require'math'

an = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
ci = 'ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ⓪①②③④⑤⑥⑦⑧⑨'
bl = '𝔄𝔅ℭ𝔇𝔈𝔉𝔊ℌℑ𝔍𝔎𝔏𝔐𝔑𝔒𝔓𝔔ℜ𝔖𝔗𝔘𝔙𝔚𝔛𝔜ℨ𝔞𝔟𝔠𝔡𝔢𝔣𝔤𝔥𝔦𝔧𝔨𝔩𝔪𝔫𝔬𝔭𝔮𝔯𝔰𝔱𝔲𝔳𝔴𝔵𝔶𝔷'

an2ci = {}
-- Circled letters are 3 bytes, just use sub string
for i=1, #an
  f = i*3+1-3
  t = i*3
  an2ci[an\sub(i,i)] = ci\sub(f, t)

an2bl = {}
i=1
-- Since blackletters have varying byte length, use the common lua pattern to find multibyte chars
for uchar in string.gfind(bl, "([%z\1-\127\194-\244][\128-\191]*)")
  an2bl[an\sub(i,i)] = uchar
  i = i +1

codepoints = (str) ->
  str\gmatch("[%z\1-\127\194-\244][\128-\191]*")

unichr = (n) ->
  html2unicode('&#x%x;'\format(n))

wireplace = (offset, arg) ->
    s = arg or ''
    t = {}
    for i = 1, #s
      bc = string.byte(s, i, i)
      -- Replace space width ideographic space for fullwidth offset
      if bc == 32 and offset == 0xFEE0
        t[#t + 1] = '\227\128\128'
      elseif bc == 32
        t[#t + 1] = ' '
      elseif bc < 0x80 then
        t[#t + 1] = html2unicode("&#" .. (offset + bc) .. ";")
      else
        t[#t + 1] = s\sub(i, i)

    table.concat(t, "")

remap = (map, s) ->
  table.concat [map[s\sub(i,i)] or s\sub(i,i) for i=1, #s], ''

zalgo = (text, intensity=50) ->
  zalgo_chars = {}
  for i=0x0300, 0x036f
    zalgo_chars[i-0x2ff] = unichr(i)

  zalgo_chars[#zalgo_chars + 1] = unichr(0x0488)
  zalgo_chars[#zalgo_chars + 0] = unichr(0x0489)

  zalgoized = {}
  for letter in codepoints(text)
    zalgoized[#zalgoized + 1] = letter
    zalgo_num = math.random(1, intensity)
    for i=1, zalgo_num
      zalgoized[#zalgoized + 1] = zalgo_chars[math.random(1, #zalgo_chars)]
  table.concat(zalgoized)

PRIVMSG:
  '^%pwide (.+)$': (source, destination, arg) =>
    @Msg 'privmsg', destination, source, wireplace(0xFEE0, arg)
  '^%pblackletter (.+)$': (source, destination, arg) =>
    @Msg 'privmsg', destination, source, remap(an2bl, arg)
  '^%pcircled (.+)$': (source, destination, arg) =>
    @Msg 'privmsg', destination, source, remap(an2ci, arg)
  '^%pzalgo (.+)$': (source, destination, arg) =>
    @Msg 'privmsg', destination, source, zalgo(arg, 10)


utf8: add more maps

utf8: add more maps

Former-commit-id: 69446502cb1cfe39b2da5ae4487feb99455f6255 [formerly ab4aa162def3c219e9dae0444777389d510f3de9]
Former-commit-id: 3f85de09bb400bdb9df11f766436ebb78f0c66e3

html2unicode = require'html'
math = require'math'

an = [[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789,.?!"'`()[]{}<>&_]]
charmaps = {
ci: 'ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ⓪①②③④⑤⑥⑦⑧⑨'
bl: '𝔄𝔅ℭ𝔇𝔈𝔉𝔊ℌℑ𝔍𝔎𝔏𝔐𝔑𝔒𝔓𝔔ℜ𝔖𝔗𝔘𝔙𝔚𝔛𝔜ℨ𝔞𝔟𝔠𝔡𝔢𝔣𝔤𝔥𝔦𝔧𝔨𝔩𝔪𝔫𝔬𝔭𝔮𝔯𝔰𝔱𝔲𝔳𝔴𝔵𝔶𝔷'
ud: [[∀BƆDƎℲפHIſK˥WNOԀQRS┴∩ΛMX⅄Zɐqɔpǝɟƃɥᴉɾʞlɯuodbɹsʇnʌʍxʎz0ƖᄅƐㄣϛ9ㄥ86'˙¿¡,,,)(][}{><⅋‾]]
nc: [[🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩🅐🅑🅒🅓🅔🅕🅖🅗🅘🅙🅚🅛🅜🅝🅞🅟🅠🅡🅢🅣🅤🅥🅦🅧🅨🅩⓿]]
sq: [[🄰🄱🄲🄳🄴🄵🄶🄷🄸🄹🄺🄻🄼🄽🄾🄿🅀🅁🅂🅃🅄🅅🅆🅇🅈🅉🄰🄱🄲🄳🄴🄵🄶🄷🄸🄹🄺🄻🄼🄽🄾🄿🅀🅁🅂🅃🅄🅅🅆🅇🅈🅉0123456789,⊡]]
ns: [[🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾🅿🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾🅿🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉]]
ds: [[𝔸𝔹ℂ𝔻𝔼𝔽𝔾ℍ𝕀𝕁𝕂𝕃𝕄ℕ𝕆ℙℚℝ𝕊𝕋𝕌𝕍𝕎𝕏𝕐ℤ𝕒𝕓𝕔𝕕𝕖𝕗𝕘𝕙𝕚𝕛𝕜𝕝𝕞𝕟𝕠𝕡𝕢𝕣𝕤𝕥𝕦𝕧𝕨𝕩𝕪𝕫𝟘𝟙𝟚𝟛𝟜𝟝𝟞𝟟𝟠𝟡]]
bo: [[𝐀𝐁𝐂𝐃𝐄𝐅𝐆𝐇𝐈𝐉𝐊𝐋𝐌𝐍𝐎𝐏𝐐𝐑𝐒𝐓𝐔𝐕𝐖𝐗𝐘𝐙𝐚𝐛𝐜𝐝𝐞𝐟𝐠𝐡𝐢𝐣𝐤𝐥𝐦𝐧𝐨𝐩𝐪𝐫𝐬𝐭𝐮𝐯𝐰𝐱𝐲𝐳𝟎𝟏𝟐𝟑𝟒𝟓𝟔𝟕𝟖𝟗]]
bi: [[𝑨𝑩𝑪𝑫𝑬𝑭𝑮𝑯𝑰𝑱𝑲𝑳𝑴𝑵𝑶𝑷𝑸𝑹𝑺𝑻𝑼𝑽𝑾𝑿𝒀𝒁𝒂𝒃𝒄𝒅𝒆𝒇𝒈𝒉𝒊𝒋𝒌𝒍𝒎𝒏𝒐𝒑𝒒𝒓𝒔𝒕𝒖𝒗𝒘𝒙𝒚𝒛0123456789]]
bs: [[𝓐𝓑𝓒𝓓𝓔𝓕𝓖𝓗𝓘𝓙𝓚𝓛𝓜𝓝𝓞𝓟𝓠𝓡𝓢𝓣𝓤𝓥𝓦𝓧𝓨𝓩𝓪𝓫𝓬𝓭𝓮𝓯𝓰𝓱𝓲𝓳𝓴𝓵𝓶𝓷𝓸𝓹𝓺𝓻𝓼𝓽𝓾𝓿𝔀𝔁𝔂𝔃]]
pt: [[ ⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵0⑴⑵⑶⑷⑸⑹⑺⑻⑼]]
}
codepoints = (str) ->
  str\gmatch("[%z\1-\127\194-\244][\128-\191]*")

-- Construct a table which can be used for lookup replacement later
-- Iterate over normal ascii and find counterparts in the weirdo strings as substrings
maps = {}
for charmap, chars in pairs charmaps
  i = 1
  maps[charmap] = {}
  for uchar in string.gfind(chars, "([%z\1-\127\194-\244][\128-\191]*)")
    maps[charmap][an\sub(i,i)] = uchar
    i = i +1

unichr = (n) ->
  html2unicode('&#x%x;'\format(n))

wireplace = (offset, arg) ->
    s = arg or ''
    t = {}
    for i = 1, #s
      bc = string.byte(s, i, i)
      -- Replace space width ideographic space for fullwidth offset
      if bc == 32 and offset == 0xFEE0
        t[#t + 1] = '\227\128\128'
      elseif bc == 32
        t[#t + 1] = ' '
      elseif bc < 0x80 then
        t[#t + 1] = html2unicode("&#" .. (offset + bc) .. ";")
      else
        t[#t + 1] = s\sub(i, i)

    table.concat(t, "")

remap = (map, s) ->
  table.concat [map[s\sub(i,i)] or s\sub(i,i) for i=1, #s], ''

zalgo = (text, intensity=50) ->
  zalgo_chars = {}
  for i=0x0300, 0x036f
    zalgo_chars[i-0x2ff] = unichr(i)

  zalgo_chars[#zalgo_chars + 1] = unichr(0x0488)
  zalgo_chars[#zalgo_chars + 0] = unichr(0x0489)

  zalgoized = {}
  for letter in codepoints(text)
    zalgoized[#zalgoized + 1] = letter
    zalgo_num = math.random(1, intensity)
    for i=1, zalgo_num
      zalgoized[#zalgoized + 1] = zalgo_chars[math.random(1, #zalgo_chars)]
  table.concat(zalgoized)

PRIVMSG:
  '^%pwide (.+)$': (source, destination, arg) =>
    say wireplace(0xFEE0, arg)
  '^%pblackletter (.+)$': (source, destination, arg) =>
    say remap(maps.bl, arg)
  '^%pcircled (.+)$': (source, destination, arg) =>
    say remap(maps.ci, arg)
  '^%pzalgo (.+)$': (source, destination, arg) =>
    say zalgo(arg, 7)
  '^%pupsidedown (.+)$': (source, destination, arg) =>
    say remap(maps.ud, arg)
  '^%pflip (.+)$': (source, destination, arg) =>
    say remap(maps.ud, arg)
  '^%pparanthesized (.+)$': (source, destination, arg) =>
    say remap(maps.pt, arg)
  '^%pnegcircle (.+)$': (source, destination, arg) =>
    say remap(maps['nc'], arg)
  '^%psquare (.+)$': (source, destination, arg) =>
    say remap(maps.sq, arg)
  '^%pnegsquare (.+)$': (source, destination, arg) =>
    say remap(maps.ns, arg)
  '^%pdoublestruck (.+)$': (source, destination, arg) =>
    say remap(maps.ds, arg)
  '^%pubold (.+)$': (source, destination, arg) =>
    say remap(maps.bo, arg)
  '^%pbolditalic (.+)$': (source, destination, arg) =>
    say remap(maps.bi, arg)
  '^%pboldscript (.+)$': (source, destination, arg) =>
    say remap(maps.bs, arg)

html2unicode = require'html'
math = require'math'

an = [[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789,.?!"'`()[]{}<>&_]]
ci = 'ⒶⒷⒸⒹⒺⒻⒼⒽⒾⒿⓀⓁⓂⓃⓄⓅⓆⓇⓈⓉⓊⓋⓌⓍⓎⓏⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ⓪①②③④⑤⑥⑦⑧⑨'
bl = '𝔄𝔅ℭ𝔇𝔈𝔉𝔊ℌℑ𝔍𝔎𝔏𝔐𝔑𝔒𝔓𝔔ℜ𝔖𝔗𝔘𝔙𝔚𝔛𝔜ℨ𝔞𝔟𝔠𝔡𝔢𝔣𝔤𝔥𝔦𝔧𝔨𝔩𝔪𝔫𝔬𝔭𝔮𝔯𝔰𝔱𝔲𝔳𝔴𝔵𝔶𝔷'
ud = [[∀BƆDƎℲפHIſK˥WNOԀQRS┴∩ΛMX⅄Zɐqɔpǝɟƃɥᴉɾʞlɯuodbɹsʇnʌʍxʎz0ƖᄅƐㄣϛ9ㄥ86'˙¿¡,,,)(][}{><⅋‾]]

an2ci = {}
-- Circled letters are 3 bytes, just use sub string
for i=1, #an
  f = i*3+1-3
  t = i*3
  an2ci[an\sub(i,i)] = ci\sub(f, t)

an2bl = {}
i=1
-- Since blackletters have varying byte length, use the common lua pattern to find multibyte chars
for uchar in string.gfind(bl, "([%z\1-\127\194-\244][\128-\191]*)")
  an2bl[an\sub(i,i)] = uchar
  i = i +1

an2ud = {}
i=1
for uchar in string.gfind(ud, "([%z\1-\127\194-\244][\128-\191]*)")
  an2ud[an\sub(i,i)] = uchar
  i = i +1

codepoints = (str) ->
  str\gmatch("[%z\1-\127\194-\244][\128-\191]*")

unichr = (n) ->
  html2unicode('&#x%x;'\format(n))

wireplace = (offset, arg) ->
    s = arg or ''
    t = {}
    for i = 1, #s
      bc = string.byte(s, i, i)
      -- Replace space width ideographic space for fullwidth offset
      if bc == 32 and offset == 0xFEE0
        t[#t + 1] = '\227\128\128'
      elseif bc == 32
        t[#t + 1] = ' '
      elseif bc < 0x80 then
        t[#t + 1] = html2unicode("&#" .. (offset + bc) .. ";")
      else
        t[#t + 1] = s\sub(i, i)

    table.concat(t, "")

remap = (map, s) ->
  table.concat [map[s\sub(i,i)] or s\sub(i,i) for i=1, #s], ''

zalgo = (text, intensity=50) ->
  zalgo_chars = {}
  for i=0x0300, 0x036f
    zalgo_chars[i-0x2ff] = unichr(i)

  zalgo_chars[#zalgo_chars + 1] = unichr(0x0488)
  zalgo_chars[#zalgo_chars + 0] = unichr(0x0489)

  zalgoized = {}
  for letter in codepoints(text)
    zalgoized[#zalgoized + 1] = letter
    zalgo_num = math.random(1, intensity)
    for i=1, zalgo_num
      zalgoized[#zalgoized + 1] = zalgo_chars[math.random(1, #zalgo_chars)]
  table.concat(zalgoized)

PRIVMSG:
  '^%pwide (.+)$': (source, destination, arg) =>
    say wireplace(0xFEE0, arg)
  '^%pblackletter (.+)$': (source, destination, arg) =>
    say remap(an2bl, arg)
  '^%pcircled (.+)$': (source, destination, arg) =>
    say remap(an2ci, arg)
  '^%pzalgo (.+)$': (source, destination, arg) =>
    say zalgo(arg, 10)
  '^%pupsidedown (.+)$': (source, destination, arg) =>
    say remap(an2ud, arg)
  '^%pflip (.+)$': (source, destination, arg) =>
    say remap(an2ud, arg)


test test test

test test test

--
-- Copyright (C) 2017 DBot
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--

-- Texture indexes
-- 1    =   models/ppm/base/eye_l
-- 2    =   models/ppm/base/eye_r
-- 3    =   models/ppm/base/body
-- 4    =   models/ppm/base/horn
-- 5    =   models/ppm/base/wings
-- 6    =   models/ppm/base/hair_color_1
-- 7    =   models/ppm/base/hair_color_2
-- 8    =   models/ppm/base/tail_color_1
-- 9    =   models/ppm/base/tail_color_2
-- 10   =   models/ppm/base/cmark
-- 11   =   models/ppm/base/eyelashes

PPM2.BodyDetailsMaterials = {
    nil
    Material('models/ppm/partrender/body_leggrad1.png')
    Material('models/ppm/partrender/body_lines1.png')
    Material('models/ppm/partrender/body_stripes1.png')
    Material('models/ppm/partrender/body_headstripes1.png')
    Material('models/ppm/partrender/body_freckles.png')
    Material('models/ppm/partrender/body_hooves1.png')
    Material('models/ppm/partrender/body_hooves2.png')
    Material('models/ppm/partrender/body_headmask1.png')
    Material('models/ppm/partrender/body_hooves1_crit.png')
    Material('models/ppm/partrender/body_hooves2_crit.png')
    Material('models/ppm/partrender/body_spots1.png')
}

PPM2.ApplyMaterialData = (mat, matData) ->
    for k, v in pairs matData
        switch type(v)
            when 'string'
                mat\SetString(k, v)
            when 'number'
                mat\SetInt(k, v) if math.floor(v) == v
                mat\SetFloat(k, v) if math.floor(v) ~= v

class PonyTextureController
    @BODY_TEX_ID_FEMALE = surface.GetTextureID('models/ppm/base/body')
    @BODY_TEX_ID_MALE = surface.GetTextureID('models/ppm/base/bodym')

    @BODY_MATERIAL_MALE = Material('models/ppm/base/bodym')
    @BODY_MATERIAL_FEMALE = Material('models/ppm/base/bodyf')

    new: (ent = NULL, data, compile = true, apply = true) =>
        @ent = ent
        @networkedData = data
        @id = ent\EntIndex()
        @CompileTextures() if compile
        @ApplyTextures() if compile and apply
    GetBody: =>
        if @networkedData\GetGender() == PPM2.GENDER_FEMALE
            return @FemaleMaterial
        else
            return @MaleMaterial
    CompileTextures: =>
        @CompileBody()
    ApplyTextures: =>
        @ent\SetSubMaterial(3, "!#{@GetBody()\GetName()}")

    @QUAD_POS_CONST = Vector(256, 256, 0)
    @QUAD_FACE_CONST = Vector(0, 0, -1)
    @QUAD_SIZE_CONST = 512
    __compileBodyInternal: (rt, oldW, oldH, r, g, b, texID) =>
        render.PushRenderTarget(rt)
        render.OverrideAlphaWriteEnable(true, true)
        render.SuppressEngineLighting(true)
        render.ResetModelLighting(1, 1, 1)
        cam.IgnoreZ(true)
        render.SetViewPort(0, 0, @@QUAD_SIZE_CONST, @@QUAD_SIZE_CONST)

        render.Clear(r, g, b, 255, true, true)
        cam.Start2D()
        surface.SetDrawColor(r, g, b)
        surface.SetTexture(texID)
        surface.DrawTexturedRect(0, 0, @@QUAD_SIZE_CONST, @@QUAD_SIZE_CONST)

        render.DrawQuadEasy(@@QUAD_POS_CONST, @@QUAD_FACE_CONST, @@QUAD_SIZE_CONST, @@QUAD_SIZE_CONST, Color(r, g, b), -90)

        for i = 1, PPM2.MAX_BODY_DETAILS
            detailID = @networkedData["GetBodyDetail#{i}"](@networkedData)
            detailID += 1
            mat = PPM2.BodyDetailsMaterials[detailID]
            continue if not mat
            surface.SetMaterial(mat)
            surface.DrawTexturedRect(0, 0, 512, 512)

        cam.End2D()
        render.SuppressEngineLighting(false)
        cam.IgnoreZ(false)
        render.SetViewPort(0, 0, oldW, oldH)
        render.PopRenderTarget()
        return rt
    CompileBody: =>
        textureMale = {
            'name': "PPM2.#{@id}.Body.m"
            'shader': 'UnlitGeneric'
            'data': {
                '$basetexture': 'models/ppm/base/bodym'

                '$ignorez': 1
                '$vertexcolor': 1
                '$vertexalpha': 1
                '$nolod': 1
            }
        }

        textureFemale = {
            'name': "PPM2.#{@id}.Body.f"
            'shader': 'UnlitGeneric'
            'data': {k, v for k, v in pairs textureMale.data}
        }

        textureFemale.data['$basetexture'] = 'models/ppm/base/body'

        @MaleMaterial = CreateMaterial(textureMale.name, textureMale.shader, textureMale.data)
        @FemaleMaterial = CreateMaterial(textureFemale.name, textureFemale.shader, textureFemale.data)

        PPM2.ApplyMaterialData(@MaleMaterial, textureMale.data)
        PPM2.ApplyMaterialData(@FemaleMaterial, textureFemale.data)

        {:r, :g, :b} = @networkedData\GetBodyColor()
        oldW, oldH = ScrW(), ScrH()

        Target = GetRenderTarget("#{textureMale.name}_RenderTargetMale", 512, 512, false)
        @BodyTextureMale = @__compileBodyInternal(Target, oldW, oldH, r, g, b, @@BODY_TEX_ID_MALE)
        Target = GetRenderTarget("#{textureFemale.name}_RenderTargetFemale", 512, 512, false)
        @BodyTextureFemale = @__compileBodyInternal(Target, oldW, oldH, r, g, b, @@BODY_TEX_ID_FEMALE)

        @MaleMaterial\SetTexture('$basetexture', @BodyTextureMale)
        @FemaleMaterial\SetTexture('$basetexture', @BodyTextureFemale)

        return @MaleMaterial, @FemaleMaterial

PPM2.PonyTextureController = PonyTextureController

--
-- Copyright (C) 2017 DBot
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--

-- Texture indexes
-- 1    =   models/ppm/base/eye_l
-- 2    =   models/ppm/base/eye_r
-- 3    =   models/ppm/base/body
-- 4    =   models/ppm/base/horn
-- 5    =   models/ppm/base/wings
-- 6    =   models/ppm/base/hair_color_1
-- 7    =   models/ppm/base/hair_color_2
-- 8    =   models/ppm/base/tail_color_1
-- 9    =   models/ppm/base/tail_color_2
-- 10   =   models/ppm/base/cmark
-- 11   =   models/ppm/base/eyelashes

PPM2.BodyDetailsMaterials = {
    nil
    Material('models/ppm/partrender/body_leggrad1.png')
    Material('models/ppm/partrender/body_lines1.png')
    Material('models/ppm/partrender/body_stripes1.png')
    Material('models/ppm/partrender/body_headstripes1.png')
    Material('models/ppm/partrender/body_freckles.png')
    Material('models/ppm/partrender/body_hooves1.png')
    Material('models/ppm/partrender/body_hooves2.png')
    Material('models/ppm/partrender/body_headmask1.png')
    Material('models/ppm/partrender/body_hooves1_crit.png')
    Material('models/ppm/partrender/body_hooves2_crit.png')
    Material('models/ppm/partrender/body_spots1.png')
}

PPM2.ApplyMaterialData = (mat, matData) ->
    for k, v in pairs matData
        switch type(v)
            when 'string'
                mat\SetString(k, v)
            when 'number'
                mat\SetInt(k, v) if math.floor(v) == v
                mat\SetFloat(k, v) if math.floor(v) ~= v

class PonyTextureController
    @BODY_TEX_ID_FEMALE = surface.GetTextureID('models/ppm/base/body')
    @BODY_TEX_ID_MALE = surface.GetTextureID('models/ppm/base/bodym')

    @BODY_MATERIAL_MALE = Material('models/ppm/base/bodym')
    @BODY_MATERIAL_FEMALE = Material('models/ppm/base/bodyf')

    new: (ent = NULL, data, compile = true, apply = true) =>
        @ent = ent
        @networkedData = data
        @id = ent\EntIndex()
        @CompileTextures() if compile
        @ApplyTextures() if compile and apply
    GetBody: =>
        if @data\GetGender() == PPM2.GENDER_FEMALE
            return @FemaleMaterial
        else
            return @MaleMaterial
    CompileTextures: =>
        @CompileBody()
    ApplyTextures: =>
        @ent\SetSubMaterial(3, @GetBody())
    __compileBodyInternal: (rt, oldW, oldH, r, g, b, texID) =>
        render.PushRenderTarget(rt)
        render.OverrideAlphaWriteEnable(true, true)
        render.SetViewPort(0, 0, 512, 512)

        render.Clear(r, g, b, 255, true, true)
        cam.Start2D()
        surface.SetDrawColor(r, g, b)
        surface.SetTexture(texID)
        surface.DrawTexturedRect(0, 0, 512, 512)

        for i = 1, PPM2.MAX_BODY_DETAILS
            detailID = @data["GetBodyDetail#{i}"]()
            detailID += 1
            mat = PPM2.BodyDetailsMaterials[detailID]
            continue if not mat
            surface.SetMaterial(mat)
            surface.DrawTexturedRect(0, 0, 512, 512)

        cam.End2D()

        render.SetViewPort(0, 0, oldW, oldH)
        render.PopRenderTarget()
        return rt
    CompileBody: =>
        textureMale = {
            'name': "PPM2.#{@id}.Body.Male"
            'shader': 'VertexLitGeneric'
            'data': {
                '$basetexture': 'models/ppm/base/bodym'

                '$model': '1'
                '$phong': '1'
                '$basemapalphaphongmask': '1'
                '$phongexponent': '6'
                '$phongboost': '0.05'
                '$phongalbedotint': '1'
                '$phongtint': '[1 .95 .95]'
                '$phongfresnelranges': '[0.5 6 10]'

                '$rimlight': 1
                '$rimlightexponent': 2
                '$rimlightboost': 1
            }
        }

        textureFemale = {
            'name': "PPM2.#{@id}.Body.Female"
            'shader': 'VertexLitGeneric'
            'data': {k, v for k, v in pairs textureMale.data}
        }

        textureFemale.data['$basetexture'] = 'models/ppm/base/body'

        @MaleMaterial = CreateMaterial(textureMale.name, textureMale.shader, textureMale.data)
        @FemaleMaterial = CreateMaterial(textureFemale.name, textureFemale.shader, textureFemale.data)

        PPM2.ApplyMaterialData(@MaleMaterial, textureMale.data)
        PPM2.ApplyMaterialData(@FemaleMaterial, textureFemale.data)

        {:r, :g, :b} = @data\GetBodyColor()
        oldW, oldH = ScrW(), ScrH()

        Target = GetRenderTarget("#{textureMale.name}_RenderTargetMale", 512, 512, false)
        @BodyTextureMale = @__compileBodyInternal(Target, oldW, oldH, r, g, b, @@BODY_TEX_ID_MALE)
        Target = GetRenderTarget("#{textureFemale.name}_RenderTargetFemale", 512, 512, false)
        @BodyTextureFemale = @__compileBodyInternal(Target, oldW, oldH, r, g, b, @@BODY_TEX_ID_FEMALE)

        @MaleMaterial\SetTexture('$basetexture', @BodyTextureMale)
        @FemaleMaterial\SetTexture('$basetexture', @BodyTextureFemale)

        return @MaleMaterial, @FemaleMaterial

PPM2.PonyTextureController = PonyTextureController