mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/Scribunto
synced 2024-11-29 18:44:21 +00:00
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')
|