Modul:Pinging

aus Wikipedia, der freien Enzyklopädie
Zur Navigation springen Zur Suche springen

Die Dokumentation für dieses Modul kann unter Modul:Pinging/Doku erstellt werden

local Pinging = { suite   = "Pinging",
                  serial  = "2022-11-14",
                  item    = 108829575,
                  maxEcho = 50,    -- currently permitted number of recipients
                  maxName = 99,    -- length of a user name
                  start   = "@",
                  suffix  = ":" }
--[==[
Support templates for pings and user lists
]==]
local Failsafe = Pinging


if mw.site.server:find( ".beta.wmflabs.org", 4, true ) then
    require( "strict" )
end


local function fair( analyse, add )
    -- Create link target or link title
    -- Parameter:
    --     analyse  -- string with page name or nick
    --     add      -- true, if user namespace to be added
    --                 false, if user namespace to be stripped
    -- Returns:
    --     string
    local ns = false
    local r  = analyse
    local space, spec = r:match( "^([^:]+):(.+)$" )
    if space then
        local tns = mw.site.namespaces[ space ]
        if type( tns ) == "table" then
            ns = tns.id
        end
    end
    if add then
        if not ns then
            r = string.format( "%s:%s", mw.site.namespaces.User.name, r )
        end
    elseif ns then
        if ns == 2  or  ns == 3 then
            r = spec
        end
    end
    return r
end -- fair()



local function fault( alert )
    -- Format error message by class=error
    -- Parameter:
    --     alert  -- string, error message
    -- Returns:
    --     string, HTML span
    local e = mw.html.create( "span" )
                     :addClass( "error" )
                     :css( "white-space", "nowrap" )
                     :wikitext( alert )
    return tostring( e )
end -- fault()



local function fetch( analyse, alt )
    -- Retrieve target table
    -- Parameter:
    --     analyse  -- string with page content
    --     alt      -- string with additional nick pattern, or not
    -- Returns:
    --     sequence of targets, might be empty
    local i    = 1
    local loop = true
    local r    = { }
    local s    = analyse
    local j, k
    while ( loop ) do
        loop = false
        j    = s:find( "<!--", i, true )
        if j then
            k = s:find( "-->",  j + 4,  true )
            if k then
                loop = true
                s    = s:sub( 1,  j )  ..  s:sub( k + 3 )
                i    = j
            end
        end
    end -- while loop
    i    = 1
    loop = true
    while ( loop ) do
        loop = false
        j    = s:find( "{{#target:", i, true )
        if j then
            j = j + 10
            k = s:find( "}}", j, true )
            if k then
                loop = true
                table.insert( r,  s:sub( j,  k - 1 ) )
                i = k + 1
            end
        end
    end -- while loop
    if alt then
        local n, sn, sns, st, sub, tns
        i    = 1
        loop = true
        while ( loop ) do
            j, i = mw.ustring.find( s, alt, i )
            if j then
                loop   = true
                sub    = mw.ustring.sub( s, j, i )
                st, sn = mw.ustring.match( sub, alt )
                if st then
                    st = mw.text.trim( st )
                    if st == "" then
                        st = false
                    end
                end
                if st then
                    n = 2
                    if sn then
                        sn = mw.text.trim( sn )
                        if sn == "-" then
                            n = false
                        elseif sn ~= "" then
                            sns = sn:match( "^(%d+)$" )
                            if sns then
                                n = tonumber( sns )
                            else
                                tns = mw.site.namespaces[ sns ]
                                if tns then
                                    n = tns.id
                                end
                            end
                        end
                    end
                    if n then
                        st = string.format( "%s:%s",
                                            mw.site.namespaces[ n ].name,
                                            st )
                    end
                    table.insert( r, st )
                end
            else
                loop = false
            end
        end -- while loop
    end
    return r
end -- fetch()



local function friend( assign, abroad, area, another, alter )
    -- Format single link
    -- Parameter:
    --     assign   -- user or page name
    --     abroad   -- page names came from #target
    --     area     -- true: always wide area = URL
    --     another  -- display alternative link title
    --     alter    -- link is hidden
    -- Returns:
    --     1. string or false,
    --     2. true: string is internal
    --     3. true: error occurred
    local long = area
    local r    = assign
    local site = false
    local lapsus
    if abroad then
        local s
        s, site = assign:match( "^%s*(.+)%s*|%s*(.*)%s*$" )
        if s then
            site = mw.text.trim( site )
            if site == "" then
                site = false
            else
                long = true
            end
            r = s
        end
    end
    if r then
        r = mw.text.trim( r )
        if r == "" then
            r = false
        end
    end
    if r then
        local live = not alter
        local show
        if r:find( "[#<>%[%]%{%}]" )    or
           ( not abroad   and
             ( r:find( "[/@]" )   or
               mw.ustring.len( r ) >  Pinging.maxName ) ) then
            lapsus = true
        end
        if not lapsus then
            if another then
                show = mw.text.trim( another )
                if show == "" then
                    show = false
                end
            elseif live then
                show = fair( r, false )
            end
        end
        if not show then
            show = r
        end
        if abroad then
            if site then
                show = string.format( "%s@%s", show, site )
            end
        else
            r = fair( r, true )
        end
        if show:find( "%s" )  and  live then
            show = mw.text.trim( show )
            if show:find( "%s" ) then
                local e = mw.html.create( "span" )
                                 :css( "white-space", "nowrap" )
                                 :wikitext( show )
                show = tostring( e )
            end
        end
        if lapsus then
            r    = fault( show )
            long = true
        elseif long then
            if site then
                r = string.format( "//%s/wiki/%s",
                                   site,
                                   mw.uri.encode( r, "WIKI" ) )
            else
                r = tostring( mw.uri.canonicalUrl( r ) )
            end
            r = string.format( "[%s %s]", r, show )
            if live then
                local e = mw.html.create( "span" )
                                 :addClass( "plainlinks" )
                                 :wikitext( r )
                r = tostring( e )
            end
        elseif alter  or  r == show then
            r = string.format( "[[%s]]", r )
        else
            r = string.format( "[[%s|%s]]", r, show )
        end
    end
    return r,  not long,  lapsus
end -- friend()



local function friends( assigned, abroad, area, assume, active, alter )
    -- Make list of entries
    -- Parameter:
    --     assigned  -- table with pages or users
    --     abroad    -- true: page names came from #target
    --     area      -- true: always wide area = URL
    --     assume    -- table with default options
    --     active    -- table with current options
    --     alter     -- string: hide result, show this
    -- Returns:
    --     string
    local limit  = ( not area  and
                     active.max ~= "0"  and  assume.max ~= "0" )
    local max    = Pinging.maxEcho
    local n      = 0
    local lapsus, light, lost, r, s, stick
    if alter then
        stick = " "
    elseif active[ "/" ] then
        stick = active[ "/" ]
    elseif assume[ "/" ] then
        stick = assume[ "/" ]
    else
        if Pinging.stick then
            stick = Pinging.stick
        else
            local ltr = not mw.language.getContentLanguage():isRTL()
            local e   = mw.html.create( "span" )
                               :wikitext( "&#124;" )
            local s
            if ltr then
                s = "left"
            else
                s = "right"
            end
            e:css( "margin-" .. s,  "0.5em" )
            stick = tostring( e ) .. " "
            Pinging.stick = stick
        end
    end
    if limit  and  ( active.max or assume.max ) then
        if tonumber( assume.max ) then
            max = tonumber( assume.max )
        end
        if tonumber( active.max ) then
            max = tonumber( active.max )
        end
    end
    stick = stick:gsub( "^_", " " )
                 :gsub( "_$", " " )
    for k, v in pairs( assigned ) do
        if type( k ) == "number" then
            s = assigned[ "label" .. tostring( k ) ]
            s, light, lost = friend( v, abroad, area, s, alter )
            if s then
                if r then
                    r = r .. stick
                else
                    r = ""
                end
                if lost then
                    lapsus = true
                elseif limit and light then
                    n = n + 1
                    if n > max then
                        local scream = "Echo-notification-count"
                        local tell   = mw.message.new( scream )
                        if tell:exists() then
                            scream = tell:numParams( max ):plain()
                        else
                            scream = string.format( "count %d &gt; %d",
                                                    n, max )
                        end
                        scream = string.format( "&#32;!! %s !!&#32;",
                                                scream )
                        r      = r .. fault( scream )
                        lapsus = true
                        limit  = false
                    end
                end
                r = r .. s
            end
        end
    end -- for k
    if r then
        local start  = Pinging.start
        local suffix = Pinging.suffix
        if active[ "@" ] then
            start = active[ "@" ]
        elseif assume[ "@" ] then
            start = assume[ "@" ]
        elseif active.a then
            start = active.a
        end
        if active[ ":" ] then
            suffix = active[ ":" ]
        elseif active.p then
            suffix = active.p
        elseif assume[ ":" ] then
            suffix = assume[ ":" ]
        end
        start = start:gsub( "^_", " " )
                     :gsub( "_$", " " )
        suffix = suffix:gsub( "^_", " " )
                       :gsub( "_$", " " )
        if alter  and  not lapsus then
            local e = mw.html.create( "span" )
                             :css( "display", "none" )
                             :wikitext( r )
            r = tostring( e ) .. alter
        end
        r = string.format( "%s%s%s", start, r, suffix )
    else
        r = fault( "? . ? . ? . ?" )
    end
    return r
end -- friends()



local function massmessage( assume, active )
    -- Create text from massmessage distribution page
    -- Parameter:
    --     assume  -- table with default options
    --                [ 1 ]  -- page specification
    --                [ 2 ]  -- title; none for "_", list if omitted
    --     active  -- table with current options
    -- Returns:
    --     string
    local source = assume[ 1 ]
    local page, r, targets
    if source then
        local id, story
        id = source:match( "^%s*#(%d+)%s*$" )
        if id then
            page = tonumber( id )
        else
            page = source
        end
        page = mw.title.new( page )
        if page then
            story = page:getContent()
        end
        if story then
            targets = fetch( story, assume.pattern )
        else
            r = fault( source )
        end
    end
    if targets then
        --  if   =0
        local show = assume[ 2 ]
        if show then
            show = mw.text.trim( show )
            if show == "" then
                show = false
            end
        end
        if show == "_" then
            show = ""
        elseif show then
            show = string.format( "[[%s|%s]]", page.prefixedText, show )
        end
        r = friends( targets, true, false, assume, active, show )
    elseif not r then
        r = fault( source or "massmessage|namespace:title" )
    end
    if assume.subst  and  not mw.isSubsting() then
        local s = string.format( "&#123;&#123;subst:%s&#125;&#125;",
                                 assume.subst )
        r = fault( s )
    end
    return r
end -- massmessage()



local function userlist( assume, active, area )
    -- Create text from template transclusion
    -- Parameter:
    --     assume  -- table with default options
    --     active  -- table with current options
    --     area    -- true: always wide area = URL
    -- Returns:
    --     string
    return friends( active, false, area, assume, active, false )
end -- userlist()



Pinging.f = function ( assigned, area, assume, active )
    -- Make list of entries
    -- Parameter:
    --     assigned  -- table with pages or users
    --     area      -- true: always wide area = URL
    --     assume    -- table with default options
    --     active    -- table with current options
    -- Returns:
    --     string
    local defaults = assume  or  { }
    local current  = active  or  { }
    local r
    if type( assigned ) == "table"  and
       type( defaults ) == "table"  and
       type( current ) == "table" then
        r = friends( assigned, false, area, defaults, current, false )
    else
        local s
        if type( assigned ) ~= "table" then
            s = "assigned"
        end
        if type( defaults ) ~= "table" then
            if s then
                s = s .. ", assume"
            else
                s = "assume"
            end
        end
        if type( current ) ~= "table" then
            if s then
                s = s .. ", active"
            else
                s = "active"
            end
        end
        s = s .. " not 'table'"
        r = fault( s )
    end
    return r
end -- Pinging.f()



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
    -- 2020-08-17
    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
        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 = { }

function p.massmessage( a1, a2 )
    local r
    if type( a1 ) == "table" then
        local p1, p2
        if type( a1.getParent ) == "function" then
            -- a1 is supposed to be a frame
            p1 = a1.args
            p2 = a1:getParent().args
        else
            p1 = a1
            if type( a2 ) == "table" then
                p2 = a2
            else
                p2 = { }
            end
        end
        r = massmessage( p1, p2 )
    else
        r = fault( "Pinging::massmessage()" )
    end
    return r
end

function p.maxecho()
    return tostring( Pinging.maxEcho )
end

function p.noping( frame )
    return userlist( frame.args, frame:getParent().args, true )
end

function p.ping( frame )
    return userlist( frame.args, frame:getParent().args, false )
end

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