mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Scribunto
synced 2024-11-27 01:30:00 +00:00
cebe775ee8
Package library: * Added a simulation of the Lua 5.1 package library. * Removed mw.import(), replaced it with a package loader. Packages can be retrieved from the wiki, using require('Module:Foo'), or from files distributed with Scribunto, using require('foo'). The "Module:" prefix allows for source compatibility with existing Lua code. * Added a couple of libraries from LuaForge: luabit and stringtools. * Made fetchModuleFromParser() return null on error instead of throwing an exception, to more easily support the desired behaviour of the package loader, which needs to return null on error. * Renamed mw.setupEnvironment() to mw.setup() since it is setting up things other than the environment now. * In MWServer:handleRegisterLibrary(), remove the feature which interprets dots in library names, since LuaSandbox doesn't support this. Improved module isolation and related refactoring: * Expose restricted versions of getfenv() and setfenv() to user Lua code. Requires luasandbox r114952. * Don't cache the export list returned by module execution for later function calls. This breaks isolation of #invoke calls, since the local variables are persistent. * Removed ScribuntoFunctionBase and its children, since it doesn't really have a purpose if it can't cache anything. Instead, invoke functions using a module method called invoke(). * Removed Module::initialize(), replaced it with a validate() function. This is a more elegant interface and works better with the new module caching scheme. * Use a Status object for the return value of Engine::validate() instead of an array. Use the formatting facilities of the Status class. Other: * Removed "too many returns" error, doesn't fit in with Lua conventions. * Use the standalone engine by default, so that the extension will work without configuration for more people. * Added an accessor for $engine->interpreter * Fix mw.clone() to correctly clone metatables * If the standalone interpreter exits due to an error, there are some contexts where the initial error will be caught and ignored, and the user will see the error from checkValid() instead. In this case, rethrow the original error for a more informative message. * Load mw.lua into the initial standalone environment, to reduce code duplication between mw.lua and MWServer.lua. * Fixed a bug in Scribunto_LuaStandaloneInterpreter::handleCall() for functions that return no results. * Fixed a bug in encodeLuaVar() for strings with "\r". Added test case. * In MWServer.lua, don't call error() for internal errors, instead just print the error and exit. This avoids a protocol violation when an error is encountered from within handleCall(). * Added lots of documentation. Lua doc comments are in LuaDoc format. Change-Id: Ie2fd572c362bedf02f45d3fa5352a5280e034740
332 lines
8 KiB
Lua
332 lines
8 KiB
Lua
--[[---------------
|
|
Noki v0.4
|
|
-------------------
|
|
Noki is a toolkit to convert Nokia PC Suite backuped SMS to a
|
|
unicode .txt file, which is more accessible than the original
|
|
.nfb or .nfc.
|
|
|
|
It works well for Nokia PC Suite 6.5.12 and my mobile phone is
|
|
Nokia 7360. There might be some compatibility problem if you
|
|
use earlier version of the PC Suite.
|
|
|
|
How to use:
|
|
noki.save_sms('nokia.nfb', 'sms.txt')
|
|
|
|
Under the MIT license.
|
|
|
|
Noki is a part of LuaBit(http://luaforge.net/projects/bit/).
|
|
|
|
copyright(c) 2006~2007 hanzhao (abrash_han@hotmail.com)
|
|
--]]---------------
|
|
|
|
require 'hex'
|
|
require 'bit'
|
|
|
|
do
|
|
-- globals
|
|
local RETURN = '\13\0\10\0'
|
|
local SMS_FILE = '\\MPAPI\\MESSAGES'
|
|
local SMS_INBOX = 'PIT_MESSAGE_INBOX'
|
|
local SMS_OUTBOX = 'PIT_MESSAGE_OUTBOX'
|
|
local SMS_ARCHIVEBOX = 'PIT_MESSAGE_ARCHIVE'
|
|
|
|
-- output decorator
|
|
local SMS_INBOX_DEC = '[INBOX] '
|
|
local SMS_OUTBOX_DEC = '[OUTBOX] '
|
|
local SMS_ARCHIVE_DEC = '[ARCHIVE] '
|
|
|
|
-- box type
|
|
local BoxType = {
|
|
NON = 0,
|
|
IN = 1,
|
|
OUT = 2,
|
|
ARCHIVE = 3,
|
|
}
|
|
|
|
-- feed each char with an extra \0
|
|
local function asci_to_uni(asci)
|
|
--print("-------")
|
|
local uni = ""
|
|
for i = 1, string.len(asci) do
|
|
local str = string.format('%c\0', string.byte(asci, i))
|
|
--print(string.len(str))
|
|
uni = uni .. str
|
|
|
|
end
|
|
return uni
|
|
end
|
|
|
|
local function asci_padding(asci, pad)
|
|
local uni = ""
|
|
for i = 1, string.len(asci) do
|
|
local str = string.format('%c', string.byte(asci, i))
|
|
--print(string.len(str))
|
|
uni = uni .. str .. pad
|
|
|
|
end
|
|
return uni
|
|
end
|
|
|
|
-- shrink the \0 in uni code string
|
|
local function uni_to_asci(uni)
|
|
local asci = ''
|
|
--print('uni len ' .. string.len(uni))
|
|
for i = 1, string.len(uni), 2 do
|
|
asci = asci .. string.sub(uni, i, i)
|
|
end
|
|
return asci
|
|
end
|
|
|
|
local function reader(str)
|
|
local index = 1
|
|
return function (n)
|
|
--print('reader>>> idx ' .. index .. " n " .. n)
|
|
local sub = string.sub(str, index, index + n - 1)
|
|
--[[print(hex.to_hex(string.byte(sub, 1)))
|
|
print(hex.to_hex(string.byte(sub, 2)))
|
|
print(hex.to_hex(string.byte(sub, 3)))
|
|
print(hex.to_hex(string.byte(sub, 4)))
|
|
--]]
|
|
index = index + n
|
|
return sub
|
|
end
|
|
end
|
|
|
|
local function read_number(read, n)
|
|
local str = read(n)
|
|
local rslt = 0
|
|
for i = 1, n do
|
|
local v = string.byte(str, i)
|
|
rslt = bit.bor(rslt, bit.blshift(v, (i-1)*8))
|
|
end
|
|
return rslt
|
|
end
|
|
|
|
local function read_int(read)
|
|
return read_number(read, 4)
|
|
end
|
|
|
|
|
|
local function read_short(read)
|
|
return read_number(read, 2)
|
|
end
|
|
|
|
local function read_nfb_string(read)
|
|
local len = read_int(read)
|
|
local unistr = read(len*2)
|
|
return unistr
|
|
end
|
|
|
|
local function read_nfb_header(read)
|
|
local nfb_header = {
|
|
ver = read_int(read),
|
|
firmware = read_nfb_string(read),
|
|
phone = read_nfb_string(read),
|
|
entries = read_int(read),
|
|
}
|
|
return nfb_header
|
|
end
|
|
|
|
local function read_nfb_file(read)
|
|
local nfbf = {}
|
|
nfbf.path = read_nfb_string(read)
|
|
|
|
nfbf.nbytes = read_int(read)
|
|
|
|
nfbf.bytes = read(nfbf.nbytes)
|
|
local stamp = read_int(read)
|
|
|
|
return nfbf
|
|
end
|
|
|
|
local function read_nfb_dir(read)
|
|
local nfbd = {
|
|
path = read_nfb_string(read)
|
|
}
|
|
return nfbd
|
|
end
|
|
|
|
local function save_entry(fp, tbl)
|
|
for k, v in pairs(tbl) do
|
|
fp:write(v)
|
|
fp:write(RETURN)
|
|
end
|
|
end
|
|
|
|
-- save sms entries
|
|
local function save_sms(fp, ctnt)
|
|
-- print("save sms ----")
|
|
local in_box = asci_padding(SMS_INBOX, "%z")
|
|
local out_box = asci_padding(SMS_OUTBOX, "%z")
|
|
local archive_box = asci_padding(SMS_ARCHIVEBOX, "%z")
|
|
local line_s = asci_padding("1020", "%z")
|
|
local head = asci_padding("1033", "%z")
|
|
local tail = asci_padding("1040", "%z")
|
|
local service_center_tail = asci_padding("1080", "%z")
|
|
local phone_nb = "%+%z%d%z%d%z[%d%z]+" -- default is type 145 with '+'
|
|
local phone_nb_129 = string.rep("%d%z", 11) -- phone number type 129 without '+'
|
|
local time = "[%d%z]+%-%z%d%z%d%z%-%z%d%z%d%zT%z%d%z%d%z:%z%d%z%d%z"
|
|
|
|
local pattern = "([^\10]+)\13%z\10%z"
|
|
local line_end = "\13%z\10%z"
|
|
local lineb, linee = string.find(ctnt, line_end)
|
|
local start = 1
|
|
local line_number = 1
|
|
while(lineb and linee) do
|
|
local line = string.sub(ctnt, start, lineb - 1)
|
|
--line = string.sub(ctnt, gb, ge)
|
|
local type = BoxType.NON
|
|
--print('capture ' .. string.len(line))
|
|
--print(uni_to_asci(box))
|
|
if(string.find(line, in_box)) then
|
|
fp:write(asci_to_uni(SMS_INBOX_DEC))
|
|
type = BoxType.IN
|
|
elseif(string.find(line, out_box)) then
|
|
fp:write(asci_to_uni(SMS_OUTBOX_DEC))
|
|
type = BoxType.OUT
|
|
elseif(string.find(line, archive_box)) then
|
|
fp:write(asci_to_uni(SMS_ARCHIVE_DEC))
|
|
type = BoxType.ARCHIVE
|
|
else
|
|
--print(uni_to_asci(line))
|
|
io.close(fp)
|
|
--error('unknown sms type')
|
|
|
|
return
|
|
end
|
|
|
|
hb, he = string.find(line, head)
|
|
tb, te = string.find(line, tail)
|
|
|
|
local first_number = ""
|
|
-- service center address
|
|
sb, se = string.find(line, phone_nb, tb)
|
|
--print("" .. sb .. ", " .. se)
|
|
if(sb and se) then
|
|
--print(uni_to_asci(string.sub(line, sb, se)))
|
|
-- keep the find number, if the second find for sender address fails
|
|
-- then this number is the sender address
|
|
first_number = string.sub(line, sb, se)
|
|
else
|
|
sb, se = string.find(line, phone_nb_129, tb)
|
|
if(not (sb and se)) then
|
|
--io.close(fp)
|
|
--error("error service center address")
|
|
--return
|
|
first_number = "empty number"
|
|
-- nokia's pc suite may leave the serivce center address empty
|
|
end
|
|
end
|
|
|
|
-- sender address
|
|
se_old = se
|
|
sb, se = string.find(line, phone_nb, se)
|
|
--print("" .. sb .. ", " .. se)
|
|
local sender_address = ""
|
|
if(sb and se) then
|
|
--print(uni_to_asci(string.sub(line, sb, se)))
|
|
sender_address = string.sub(line, sb, se)
|
|
else
|
|
sb, se = string.find(line, phone_nb_129, se_old)
|
|
if(not (sb and se)) then
|
|
--[[
|
|
print(line_number)
|
|
io.close(fp)
|
|
error("error sender address")
|
|
--]]
|
|
sender_address = first_number
|
|
end
|
|
end
|
|
-- write sender
|
|
fp:write(sender_address)
|
|
fp:write(" \0")
|
|
|
|
-- date time
|
|
-- out box have no date time slot
|
|
if(type ~= BoxType.OUT and first_number ~= "empty number") then
|
|
tmb, tme = string.find(line, time, se)
|
|
--print('' .. tmb .. ", " .. tme)
|
|
if(tmb and tme) then
|
|
--print(uni_to_asci(string.sub(line, tmb+1, tme)))
|
|
else
|
|
io.close(fp)
|
|
error("error reading date time")
|
|
return
|
|
end
|
|
fp:write(string.sub(line, tmb+1, tme))
|
|
end
|
|
fp:write(RETURN)
|
|
|
|
fp:write(string.sub(line, he+3, tb-3))
|
|
|
|
fp:write(RETURN)
|
|
fp:write(RETURN)
|
|
--end
|
|
start = linee + 1
|
|
lineb, linee = string.find(ctnt, line_end, linee)
|
|
line_number = line_number + 1
|
|
end
|
|
end
|
|
|
|
-- save sms from a .nfc or .nfb file to a unicode .txt file
|
|
local function save_nfx_to(from, too)
|
|
local fp = io.open(too, 'wb')
|
|
if(not fp) then
|
|
error("error opening file " .. too .. " to write")
|
|
return
|
|
end
|
|
v = string.format('%c%c', 255, 254)
|
|
-- unicode .txt 'FF FE'
|
|
fp:write(v)
|
|
|
|
-- read the .nfc file
|
|
local nokia = io.open(from, 'rb')
|
|
if(not nokia) then
|
|
error("error open file " .. from .. " to read")
|
|
end
|
|
local ctnt = nokia:read("*a")
|
|
io.close(nokia)
|
|
|
|
local read = reader(ctnt)
|
|
|
|
local header = read_nfb_header(read)
|
|
--print(header.ver)
|
|
--print(header.entries)
|
|
|
|
for i=1, header.entries do
|
|
--print(i)
|
|
local type = read_int(read)
|
|
if(type == 1) then
|
|
-- file entry
|
|
--print('file')
|
|
local fe = read_nfb_file(read)
|
|
--save_entry(fp, fe)
|
|
if(uni_to_asci(fe.path) == SMS_FILE) then
|
|
local smsctnt = fe.bytes
|
|
--print('sms len ' .. fe.nbytes)
|
|
save_sms(fp, smsctnt)
|
|
return
|
|
end
|
|
|
|
elseif(type == 2) then
|
|
-- dir entry
|
|
--print('dir')
|
|
local fd = read_nfb_dir(read)
|
|
--save_entry(fp, fd)
|
|
else
|
|
io.close(fp)
|
|
error('unknown entry type : ' .. hex.to_hex(type))
|
|
end
|
|
end
|
|
io.close(fp)
|
|
end
|
|
|
|
-- noki interface --
|
|
noki = {
|
|
save_sms = save_nfx_to
|
|
}
|
|
|
|
end -- end block
|
|
|
|
-- sample
|
|
-- noki.save_sms('nokia2.nfb', 'sms2.txt') |