Zum Inhalt springen

Modul:AccountUtl

aus Wikipedia, der freien Enzyklopädie
Vorlagenprogrammierung Diskussionen Lua Test Unterseiten
Modul Deutsch English

Modul: Dokumentation

Diese Seite enthält Code in der Programmiersprache Lua. Einbindungszahl Cirrus

Dies ist die (produktive) Mutterversion eines global benutzten Lua-Moduls.
Wenn die serial-Information nicht übereinstimmt, müsste eine Kopie hiervon in das lokale Wiki geschrieben werden.
Versionsbezeichnung auf WikiData: 2026-09-08

local AccountUtl = { suite  = "AccountUtl",
                     serial = "2026-09-08",
                     item   = 139309150 }
--[=[
Utilities for user accounts.
isAnon()
isIP()
isTemp()
isUser()
isValid()
scope()
signer()
]=]
local This     = { }
local Failsafe = AccountUtl




This.isIP = function ( account )
    -- Check whether username is matching IPv4/IPv6 scheme
    -- Precondition:
    --    account  -- string, starting with nonzero digit
    -- Postcondition:
    --    Returns boolean
    local seek  = "^[1-9][%x.:]+$"
    local r
    if account:match( seek ) then
        if account:find( ".", 2, true ) then
            local sub  = "([12]?[0-9]?[0-9])"
            local p1, p2, p3, p4
            seek = string.format( "^%s%%.%s%%.%s%%.%s$",
                                  sub, sub, sub, sub )
            p1, p2, p3, p4 = account:match( seek )
            if p1 and p2 and p3 and p4 then
                local function legal( a )
                          if a:sub( 1, 1 ):find( "[1-9]" )  and
                             tonumber( a )  <  256 then
                              return true
                          end
                      end -- legal()
                r = legal( p1 )  and
                    legal( p2 )  and
                    legal( p3 )  and
                    legal( p4 )
            end
        elseif account:find( ":", 2, true ) then
           local parts = mw.text.split( account, ":" )
           if #parts == 8 then
               r = true
               for i = 1, #parts do
                   if not parts[ i ]:match( "^%x?%x?%x?%x?$" ) then
                       r = false
                       break -- for i
                   end
               end -- for i
           end
        end
    end
    return r or false
end -- This.isIP()



This.isTemp = function ( account )
    -- Check whether username is matching temporary scheme
    -- Precondition:
    --    account  -- string, starting with "~"
    -- Postcondition:
    --    Returns boolean
    local seek = "^~(20[2-9]%d)%-([1-9]%d%d%d[%d%-]*)$"
    local y, s = account:match( seek )
    local r
    if y then
        y = tonumber( y )
        if y >= 2024 then
            local g
            s = s .. "-"
            g = s:match( "^(%d+)%-" )
            if tonumber( g ) >= 1500 then
                if s:match( "^%d+%-$" ) then
                    r = true
                elseif g:len() == 5 then
                    s = s:sub( 7 )
                    g = s:match( "^(%d+)%-" )
                    if g then
                        local n = g:len()
                        if n <= 5 then
                            if s:match( "^%d+%-$" ) then
                                r = true
                            elseif n == 5 then
                                s = s:sub( 7 )
                                if s:match( "^%d+%-$" ) then
                                    r = true
                                end
                            end
                        end
                    end
                end
            end
        end
    end
    return r or false
end -- This.isTemp()



This.isAnon = function ( account )
    -- Check whether username is matching IP or temporary scheme
    -- Precondition:
    --    account  -- string
    -- Postcondition:
    --    Returns boolean
    local s1 = account:sub( 1, 1 )
    local r
    if s1 == "~" then
        r = This.isTemp( account )
    elseif s1:find( "[1-9]" ) then
        r = This.isIP( account )
    end
    return r or false
end -- This.isAnon()



This.noIP = function ( account )
    -- Check whether lookalike username is not matching IPv4 scheme
    -- Precondition:
    --    account  -- string, starting with digit
    -- Postcondition:
    --    Returns boolean, if not an IP
    local sub  = "(%d%d?%d?)"
    local seek = string.format( "^%s%%.%s%%.%s%%.%s$",
                                sub, sub, sub, sub )
    local p1, p2, p3, p4 = account:match( seek )
    local r
    if p1 and p2 and p3 and p4 then
        local function lapsus( a )
                  if tonumber( a )  >=  256 then
                      return true
                  end
              end -- lapsus()
        r = lapsus( p1 )  or
            lapsus( p2 )  or
            lapsus( p3 )  or
            lapsus( p4 )
    else
        r = true
    end
    return r
end -- This.noIP()



AccountUtl.isAnon = function ( account )
    -- Check whether username is matching IP or temporary scheme
    -- Precondition:
    --    account  -- string, or nil derived from current page
    -- Postcondition:
    --    Returns boolean
    local s = AccountUtl.signer( account )
    local r
    if s then
        r = This.isAnon( s )
    end
    return r or false
end -- AccountUtl.isAnon()



AccountUtl.isIP = function ( account )
    -- Check whether username is matching IPv4 scheme
    -- Precondition:
    --    account  -- string, or nil derived from current page
    -- Postcondition:
    --    Returns boolean
    local s = AccountUtl.signer( account )
    local r
    if s then
        if s:sub( 1, 1 ):find( "[1-9]" ) then
            r = This.isIP( s )
        end
    end
    return r or false
end -- AccountUtl.isIP()



AccountUtl.isTemp = function ( account )
    -- Check whether username is matching temporary scheme
    -- Precondition:
    --    account  -- string, or nil derived from current page
    -- Postcondition:
    --    Returns boolean
    local s = AccountUtl.signer( account )
    local r
    if s then
        if s:sub( 1, 1 )  ==  "~" then
            local seek = "^~(20[2-9]%d)%-([1-9]%d%d%d[%d%-]*)$"
            local y
            y, s = s:match( seek )
            if y then
                y = tonumber( y )
                if y >= 2024 then
                    local g
                    s = s .. "-"
                    g = s:match( "^(%d+)%-" )
                    if tonumber( g ) >= 1500 then
                        if s:match( "^%d+%-$" ) then
                            r = true
                        elseif g:len() == 5 then
                            s = s:sub( 7 )
                            g = s:match( "^(%d+)%-" )
                            if g then
                                local n = g:len()
                                if n <= 5 then
                                    if s:match( "^%d+%-$" ) then
                                        r = true
                                    elseif n == 5 then
                                        s = s:sub( 7 )
                                        if s:match( "^%d+%-$" ) then
                                            r = true
                                        end
                                    end
                                end
                            end
                        end
                    end
                end
            end
         end
    end
    return r or false
end -- AccountUtl.isTemp()



AccountUtl.isUser = function ( account )
    -- Check whether username might be any valid account
    -- Precondition:
    --    account  -- string, or nil derived from current page
    -- Postcondition:
    --    Returns boolean
    local s = AccountUtl.signer( account )
    local r
    if s then
        if This.isAnon( s ) then
            r = true
        elseif s:sub( 1, 1 ):find( "%d" ) then
            r = This.noIP( s )
        else
            r = true
        end
    end
    return r or false
end -- AccountUtl.isUser()



AccountUtl.isValid = function ( account )
    -- Check whether username can be used as nick for registration
    -- Precondition:
    --    account  -- string, or nil derived from current page
    -- Postcondition:
    --    Returns boolean
    local s = AccountUtl.signer( account )
    local r
    if s then
        if not This.isAnon( s ) then
            if s:sub( 1, 1 ):find( "%d" ) then
                r = This.noIP( s )
            else
                r = true
            end
        end
    end
    return r or false
end -- AccountUtl.isValid()



AccountUtl.scope = function ( account )
    -- Identify the type of a presumed account name
    -- Precondition:
    --    account  -- string, or nil derived from current page
    -- Postcondition:
    --    Returns string, or false
    --            "R"  -- registered
    --            "I"  -- IP
    --            "T"  -- temporary
    local s = AccountUtl.signer( account )
    local r
    if s then
        local s1 = account:sub( 1, 1 )
        r = "R"
        if s1 == "~" then
            if This.isTemp( s ) then
                r = "T"
            end
        elseif s1:find( "[1-9]" ) then
            if This.isIP( s ) then
                r = "I"
            elseif This.noIP( s ) then
                r = false
            end
        end
    end
    return r or false
end -- AccountUtl.scope()



AccountUtl.signer = function ( adjust, again )
    -- Normalize username string, or derive from page
    -- Precondition:
    --    adjust  -- string, or not
    --    again   -- true, if recursive
    -- Postcondition:
    --    Returns normalized string of a username, or not
    local r = adjust
    local scan
    if type( r ) == "string" then
        if r:find( "%", 1, true )  and
           r:find( "%%%x%x" ) then
            r = mw.uri.decode( r, "PATH" )
        end
        if r:find( "&", 1, true ) then
            r = mw.text.decode( r )
            if r:find( "&%a+;" ) then
                r = mw.text.decode( r, true )
            end
        end
        scan = string.format( "[_%%s%c]+", 0x0A )
        r    = mw.ustring.gsub( r, scan, " " )
        scan = mw.ustring.char( 91,
                                0x200E, 0x200F,
                                0x202A, 45, 0x202E,
                                93 )
        r    = mw.ustring.gsub( r, scan, "" )
        r    = mw.text.trim( r )
        if r == "" then
            r = false
        elseif r:find( ":", 2, true ) then
            local s1, s2 = mw.ustring.match( r, "^([^:]+):(.+)$" )
            if s1 then
                local o = mw.site.namespaces[ s1 ]
                if o then
                    local n = o.id
                    if n == 2  or  n == 3 then
                        r = mw.text.trim( s2 )
                        n = r:find( "/", 1, true )
                        if n then
                            if n == 1 then
                                r = false
                            else
                                r = mw.text.trim( r:sub( 1,  n - 1 ) )
                            end
                        end
                    end
                end
            end
        end
    end
    if type( r ) == "string" then
        if mw.ustring.len( r )  >  255 then
            r = false
            -- mw:Manual:$wgMaxNameChars   MW 1.2...1.4: 32
        elseif r:find( ":", 2, true ) then
            if not This.isIP( r ) then
                r = false
            end
        else
            scan = "[@#<>|%[%]%{%}/:]"
            -- mw:Manual:$wgInvalidUsernameCharacters
            if mw.ustring.find( r, scan ) then
                r = false
            elseif r:sub( 1, 2 ) == "~2" then
                if not This.isTemp( r ) then
                    r = false
                end
            elseif r:match( "^%d+%.%d+%.%d+%.%d+$" ) then
                if not This.isIP( r ) then
                    r = false
                end
            end
        end
    else
        local t = mw.title.getCurrentTitle()
        if t.namespace == 2  or  t.namespace == 3 then
            r = t.rootText
            if not again then
                r = AccountUtl.signer( r, true )
            end
        else
            r = false
        end
    end
    return r
end -- AccountUtl.signer()



Failsafe.failsafe = function ( atleast )
    -- Retrieve versioning and check for compliance
    -- Precondition:
    --     atleast  -- string, with required version
    --                         or wikidata|item|~|@ or false
    -- Postcondition:
    --     Returns  string  -- with queried version/item, also if problem
    --              false   -- if appropriate
    -- 2024-03-01
    local since  = atleast
    local last   = ( since == "~" )
    local linked = ( since == "@" )
    local link   = ( since == "item" )
    local r
    if last  or  link  or  linked  or  since == "wikidata" then
        local item = Failsafe.item
        since = false
        if type( item ) == "number"  and  item > 0 then
            local suited = string.format( "Q%d", item )
            if link then
                r = suited
            else
                local entity = mw.wikibase.getEntity( suited )
                if type( entity ) == "table" then
                    local seek = Failsafe.serialProperty or "P348"
                    local vsn  = entity:formatPropertyValues( seek )
                    if type( vsn ) == "table"  and
                       type( vsn.value ) == "string"  and
                       vsn.value ~= "" then
                        if last  and  vsn.value == Failsafe.serial then
                            r = false
                        elseif linked then
                            if mw.title.getCurrentTitle().prefixedText
                               ==  mw.wikibase.getSitelink( suited ) then
                                r = false
                            else
                                r = suited
                            end
                        else
                            r = vsn.value
                        end
                    end
                end
            end
        elseif link then
            r = false
        end
    end
    if type( r ) == "nil" then
        if not since  or  since <= Failsafe.serial then
            r = Failsafe.serial
        else
            r = false
        end
    end
    return r
end -- Failsafe.failsafe()



-- Export
local p = { }

This.is = function ( access, frame )
    return AccountUtl[ "is" .. access ]( frame.args[ 1 ] )  and  "1"
           or  ""
end -- is()

function p.isAnon( frame )
    return This.is( "Anon", frame )
end -- p.isAnon

function p.isIP( frame )
    return This.is( "IP", frame )
end -- p.isIP

function p.isTemp( frame )
    return This.is( "Temp", frame )
end -- p.isTemp

function p.isUser( frame )
    return This.is( "User", frame )
end -- p.isUser

function p.isValid( frame )
    return This.is( "Valid", frame )
end -- p.isValid

function p.scope( frame )
    return AccountUtl.scope( frame.args[ 1 ] )
end -- p.scope

function p.signer( frame )
    return AccountUtl.signer( frame.args[ 1 ] )
end -- p.signer

p.failsafe = function ( frame )
    -- Versioning interface
    local s = type( frame )
    local since
    if s == "table" then
        since = frame.args[ 1 ]
    elseif s == "string" then
        since = frame
    end
    if since then
        since = mw.text.trim( since )
        if since == "" then
            since = false
        end
    end
    return Failsafe.failsafe( since )  or  ""
end -- p.failsafe

setmetatable( p,  { __call = function ( func, ... )
                                 setmetatable( p, nil )
                                 return Failsafe
                             end } )

return p