モジュール:Cite
このモジュールについての説明文ページを モジュール:Cite/doc に作成できます
--- Generates citations across the wiki to ensure standard formatting.
-- The module accepts several different types of citations and processes each
-- differently, based on the <code>f</code> table's functions.
-- @module cite
-- @alias p
-- @require Module:Date
-- @require Module:User error
-- @require Module:Yesno
-- @require Module:Tags
-- @require Module:Cite/data
-- @author [[User:KockaAdmiralac|KockaAdmiralac]]
-- @author [[User:Ceruleanwarbler2|Ceruleanwarbler2]]
-- @author [[User:Jacky720|Jacky720]]
-- @see [[:Category:Citation templates]]
-- <nowiki>
local p = {}
require('strict')
-- Module dependencies
local Date = require('Module:Date')
local yesno = require('Module:Yesno')
local tags = require('Module:Tags')
local data = mw.loadData('Module:Cite/data')
-- Private logic.
local DATE_FORMAT = '%B %d, %Y.'
--- Wrapper for <code>userError</code> that places the page under the Pages with
-- user errors category.
-- @function err
-- @param {string} text Text to display as an error
-- @return {string} Wikitext with the error and category
-- @local
local function err(text)
return require('Module:User error')(text, 'Pages with user errors')
end
--- Checks whether a given date string is a valid date.
-- @function valid_date
-- @param {string} d Date string to check
-- @return {bool} Whether the string is a valid date
-- @local
local function valid_date(d)
return pcall(function()
Date(d)
end)
end
--- Table of possible citation types and the way they are processed.
-- Each type has a function associated with it that gets passed the arguments
-- after the type, does validation of these arguments and returns the whole
-- citation text.
-- @table cite_functions
-- @alias f
local f = {}
--- Handles Twitter citations.
-- @function f.twitter
-- @param {table} args Citation type arguments
-- @return {string} Citation text
-- @see [[Template:Cite twitter]]
-- @see [[github:client9/snowflake2time]]
function f.twitter(args)
-- Check validity of arguments
if not args.id or not tonumber(args.id) then
return err('Tweet snowflake invalid or not specified')
end
if not args.author or not args['text'] then
return err('Tweet author or citation not specified')
end
if args.wayback and not tonumber(args.wayback) then
return('Archive ID invalid')
end
if args.archivetoday and (not args.archivetimestamp or not valid_date(args.archivetimestamp)) then
return err('Date of archivation not specified')
end
-- Twitter snowflake date extraction
local snowflake = tonumber(args.id)
local epoch = math.floor(snowflake / 4194304 + 1288834974657)
local date1 = Date(math.floor(epoch / 1000))
-- Format citation
local date2
if args.archivetimestamp then
date2 = Date(args.archivetimestamp):fmt(DATE_FORMAT)
elseif args.wayback then
date2 = Date(args.wayback):fmt(DATE_FORMAT)
end
local deadurl = yesno(args['deleted'], false)
local str = {
'\'\'',
args.text,
'\'\' - ['
}
if deadurl and args.archivetoday then
table.insert(str, 'https://archive.today/')
table.insert(str, args.archivetoday)
table.insert(str, '/')
else
if deadurl and args.wayback then
table.insert(str, 'https://web.archive.org/web/')
table.insert(str, args.wayback)
table.insert(str, '/')
end
table.insert(str, 'https://twitter.com/')
table.insert(str, args.author)
table.insert(str, '/status/')
table.insert(str, args.id)
end
if data.twitter[args.author] then
table.insert(str, ' ')
table.insert(str, data.twitter[args.author])
table.insert(str, ' (@')
table.insert(str, args.author)
table.insert(str, ')')
else
table.insert(str, ' @')
table.insert(str, args.author)
end
table.insert(str, ' on Twitter,] ')
table.insert(str, date1:fmt(DATE_FORMAT))
if deadurl then
if args.wayback or args.archivetoday then
table.insert(str, ' Archived on ')
table.insert(str, date2)
else
table.insert(str, ' \'\'\'[deleted]\'\'\'')
end
end
return table.concat(str)
end
--- Handles Bluesky citations.
-- @function f.bluesky
-- @param {table} args Citation type arguments
-- @return {string} Citation text
-- @see [[Template:Cite bluesky]]
function f.bluesky(args)
if not args.author or not args.text then
return err('Author or quote not specified')
end
if not args.id then
return err('Post ID not specified')
end
if not args.timestamp or not valid_date(args.timestamp) then
return err('Date invalid or not specified')
end
return table.concat({
'\'\'',
tags.replace(args['text']),
'\'\' - [https://bsky.app/profile/',
args['author'],
'/post/',
args['id'],
' @',
args['author'],
' on Bluesky,] ',
Date(args.timestamp):fmt(DATE_FORMAT)
})
end
--- Handles citations of text in the game.
-- @function f.game
-- @param {table} frame_args Citation type arguments
-- @return {string} Citation text
-- @see [[Template:Cite game]]
function f.game(frame_args)
local args = {}
for i, v in ipairs(frame_args) do
-- frame.args is a metatable, so we cannot use #args or table.concat
-- on it before we convert it to a real table.
args[i] = v
end
local num_args = #args
if num_args == 0 then
return err('Quote not specified')
end
if num_args == 1 then
return err('Quote author not specified')
end
local author = args[num_args]
args[num_args] = nil
return table.concat({
'\'\'',
tags.replace(table.concat(args, '<br />')),
'\'\' - ',
author
})
end
--- Handles citations of YouTube videos.
-- @function f.youtube
-- @param {table} args Citation type arguments
-- @return {string} Citation text
-- @see [[Template:Cite youtube]]
function f.youtube(args)
if not args.id or not args.title then
return err('Video ID or title not specified')
end
local str = {
'\'\'',
args.title,
'\'\' - [https://youtu.be/',
args.id
}
if args.timestamp then
table.insert(str, '?t=')
table.insert(str, args.timestamp)
end
table.insert(str, ' YouTube]')
local seconds = tonumber(args.timestamp)
if seconds ~= nil then
local minutes = math.floor(seconds / 60)
seconds = seconds - minutes * 60
local hours = math.floor(minutes / 60)
minutes = minutes - hours * 60
if hours > 0 then
table.insert(str, string.format(
' (%02d:%02d:%02d)',
hours,
minutes,
seconds
))
else
table.insert(str, string.format(
' (%02d:%02d)',
minutes,
seconds
))
end
end
return table.concat(str)
end
--- Handles Tumblr citations.
-- @function f.tumblr
-- @param {table} args Citation type arguments
-- @return {string} Citation text
-- @see [[Template:Cite tumblr]]
function f.tumblr(args)
if not args.author or not args.text then
return err('Author or quote not specified')
end
if not args.id or not tonumber(args.id) then
return err('Post ID invalid or not specified')
end
if not args.timestamp or not valid_date(args.timestamp) then
return err('Date invalid or not specified')
end
return table.concat({
'\'\'',
tags.replace(args.text),
'\'\' - [https://',
-- Temporary solution to avoid showing reblogger's name as author name
-- on inaccessible posts.
-- To be replaced by proper archival parameters.
args.reblogger or args.author,
'.tumblr.com/post/',
args.id,
' ',
args.author,
' on Tumblr,] ',
Date(args.timestamp):fmt(DATE_FORMAT)
})
end
--- Handles citations of the game's code.
-- @function f.code
-- @param {table} args Citation type arguments
-- @return {string} Citation text
-- @see [[Template:Cite code]]
-- @see [[code:|Code repository]]
function f.code(args)
local str = {}
local hasLink = args.chapter == nil or tonumber(args.chapter) < 3
if not args.script then
return err('Script name not specified')
end
if args.line and not tonumber(args.line) then
return err('Starting line number is not a number')
end
if args.line2 and not tonumber(args.line2) then
return err('Ending line number is not a number')
end
if args.chapter == nil then
str = {
'[[code:',
args.script,
}
elseif hasLink then
str = {
'[[code:',
'ch',
args.chapter,
'/',
args.script,
}
end
if hasLink then
if args.line then
table.insert(str, '#L')
table.insert(str, args.line)
end
table.insert(str, '|')
end
table.insert(str, args.script)
table.insert(str, ' script')
if hasLink then
table.insert(str, ']]')
end
if args.line then
table.insert(str, ', line')
if args.line2 then
table.insert(str, 's ')
table.insert(str, args.line)
table.insert(str, '–')
table.insert(str, args.line2)
else
table.insert(str, ' ')
table.insert(str, args.line)
end
end
if args.chapter ~= nil then
table.insert(str, ', Chapter ')
table.insert(str, args.chapter)
end
return table.concat(str)
end
--- Handles citations from news sources.
-- @function f.news
-- @param {table} args Citation type arguments
-- @return {string} Citation text
-- @see [[Template:Cite news]]
function f.news(args)
if not args.text then
return err('Relevant news post excerpt not specified')
end
if not args.title then
return err('News post title not specified')
end
if not args.site then
return err('News site name not specified')
end
if not args.url then
return err('News post URL not specified')
end
if not args.timestamp or not valid_date(args.timestamp) then
return err('News post date invalid or not specified')
end
local author = {}
if args.firstname then
if args.lastname then
table.insert(author, args.lastname)
table.insert(author, ', ')
end
table.insert(author, args.firstname)
table.insert(author, ', ')
end
return table.concat({
'\'\'',
tags.replace(args.text),
'\'\' - [',
args.url,
' ',
args.title,
'] (',
table.concat(author),
Date(args.timestamp):fmt(DATE_FORMAT),
') \'\'',
args.site,
'\'\'.'
})
end
--- Handles citations from Twitch clips.
-- @function f.twitch
-- @param {table} args Citation type arguments
-- @return {string} Citation text
-- @see [[uty:Template:Cite twitch]]
function f.twitch(args)
local id = args[1] or args.id
local title = args[2] or args.title
local author = args[3] or args.author
local timestamp = args[4] or args.timestamp
if not id then
return err('Clip URL segment not specified')
end
if not title then
return err('Clip title not specified')
end
if not author then
return err('Streamer username not specified')
end
if not timestamp or not valid_date(timestamp) then
return err('Stream date invalid or not specified')
end
return table.concat({
'"\'\'[https://clips.twitch.tv/',
id,
' "',
title,
',]\'\'" a clip from [https://twitch.tv/',
mw.ustring.lower(author),
' @',
name,
'\'s] Twitch stream on ',
Date(timestamp):fmt(DATE_FORMAT)
})
end
-- Package items.
for mname, method in pairs(f) do
p[mname] = function(frame)
return method(frame:getParent().args)
end
end
return p