Module:Soyjak categorizer2
| This module is rated as beta. It is considered ready for widespread use, but should be monitored to ensure results are as expected. |
| This module depends on the following other modules: |


This module assigns categories to pages using {{Infobox soyjak}}. It distinguishes variants from subvariants, and categorizes them by origin year, parent variant (for subvariants), site of origin, and their SoyBooru post count. It relies on Module:Infobox categorizer for reading fields, alias matching, resolving overrides and issuing category links.
If the module produces a redlink category, or in other words, a category not yet created, it will state this in the preview with recommendation on what to do and put the page into a maintenance category.
It is only intended to be used for {{Infobox soyjak}}. Do not invoke this module in any other infobox, template, etc.
Rules
| Parameter | Handling |
|---|---|
type
|
The module expects to read whether the infobox describes a variant or subvariant from this field. A recognized subvariant alias takes precedence over a variant alias. Blank or unrecognized types default to variant, unrecognized nonblank types receive a silent maintenance category. |
origin
|
The module expects to read the origin year and site from this field. The first distinct four-digit year is used, more than one is considered ambiguous and is put in a silent maintenance category. Each matched site can issue a category. |
based_on
|
The module expects to read the subject parent variant from this field. Only mainspace wikilink targets will be read.[a] Recognized parent aliases are converted to "canonical" variant names.[b] Accepts up to three distinct parents, more than three is considered excessive and results in a silent maintenance category. |
booru_posts
|
The module expects to read Module:CatMark-provided post counts. Post count categories only apply to variants. If the expected count marker from CatMark is absent, a silent maintenance category is issued. |
All other infobox fields do not affect this module's categories.
Type
Type aliases are defined in the subject config.
Type matching uses cleaned text and defaults to compact substring matching. If necessary, individual alias records can specify another matching mode in config.
The module chooses one classification for variant and subvariant. Type detection is important for this module to issue categories correctly. Subvariant has precedence over variant.
Type is also read to see if a match for "Trend" is found, in which case it will issue a preview warning recommending the infobox be switched to {{Infobox trend}} and put the page in a maintenance category.
Origin year
A detected year produces a category such as Category:2020 variants or Category:2020 subvariants. If no year is found, the module places the page in an undated-year category.
Years are read in their order of appearance in wikitext, not sorted chronologically. For example, 2020 or 2019 will result in 2020 being picked. The page is put in a maintenance category for multiple possible origin years. Ranges, such as 2020 - 2022, are not expanded or interpreted as a period and result in the same maintenance behavior.
References and all other "hidden" material removed by the toolkit's text cleaner cannot supply years.
Site of origin
Site records are loaded from Module:Infobox categorizer/sites. Matching uses cleaned origin text and any domains configured in those records. The default alias matching mode is compact substring matching, customization can be done at config.
The module will accept internal links and external links during reading.
Each matched site produces a category using the resolved classification, such as 4chan variants or SoyBooru subvariants.
Subject configs can use siteBuckets to map canonical site names to broader labels. Unmapped names pass through unchanged. This controls whether individual sites are grouped into categories such as Other-site variants. Simply put, it's a bucket you can use to put multiple sites into one group.
Parent variants

Parent records are loaded from Module:Infobox categorizer/variants. If a recognized alias is found, it is converted to its canonical variant before categorization.
For example
|type = Soyjak subvariant
|based_on = [[Wholesome Soyjak]]
Since you used an alias, and this alias is recorded, this will produce Category:Gapejak subvariants.
Display labels and section fragments do not change the linked target. If, for example, Gapejak had a "Wholesome soyjak" section as [[Gapejak#Wholesome Soyjak]] or [[Gapejak#History|Wholesome soyjak]] instead of its own page, the outcome will still be the same.
Plain text does not supply a parent. Unrecognized but valid mainspace link targets still pass through as parent names. The module does not follow redirects, but the name of the redirect can still be recognized as an alias.
When more than three distinct canonical parents are detected, the first three are categorized, and the multiple-parents maintenance category is added. Parent categories and parent-detection maintenance apply only to subvariants unless disabled.
Configuration and shared data
| Page | Purpose |
|---|---|
| Module:Soyjak categorizer2/config | Exclusive config for this module. Type aliases, category patterns, post-count bands, maintenance categories, infobox misuse detection[c], site grouping, and allowed namespaces. |
| Module:Infobox categorizer/sites | Global site records, holds "canonical" site labels, aliases, and optional domains used to identify websites of origin. |
| Module:Infobox categorizer/variants | You are probably looking for this if you came to this doc for help. Global variant records, holds "canonical" variant names and aliases used to identify parent variants. Easily editable. |
Rows shaded blue contain "global" data also used by other subject modules.
Notes
- ↑ Even if you write "Bernd", the module will not recognize it as a parent until it is properly linked!
- ↑ For example, Gapejak is also known as "Guinness World Record Soyjak". This is impossible for the module to know beforehand, so a config supplies this information to correct the category link it is going to produce.
- ↑ If the infobox detects "Trend" in type, it will put up a warning, recommending use of {{Infobox trend}} and issue a maintenance category.
local p = {}
local core = require('Module:Infobox categorizer')
local config = mw.loadData('Module:Soyjak categorizer2/config')
local where = 'Module:Soyjak categorizer2/config'
local dims = core.need(config, 'dimensions', where)
local maintenance = core.need(config, 'maintenance', where)
local invalidOverrideCat = core.need(maintenance, 'invalidOverride', where .. '.maintenance')
local usingOverrideCat = core.need(maintenance, 'usingOverride', where .. '.maintenance')
local D = {}
for _, key in ipairs({
'type',
'year',
'site',
'parent',
'posts',
}) do
D[key] = core.need(dims, key, where .. '.dimensions')
end
local types = core.compileAliasSet({ core.need(config, 'types', where) }, { match = 'substring' })
local sites = core.compileAliasSet({ mw.loadData('Module:Infobox categorizer/sites') }, { match = 'substring' })
local variants = core.compileAliasSet({ mw.loadData('Module:Infobox categorizer/variants') })
local siteBucket = core.fromMap(config.siteBuckets or {}) -- unused now, for future folding split sites back into Other-site categories
local misuseCfg = core.need(config, 'misuse', where)
local misuse =
core.compileAliasSet({ { { category = 'trend', aliases = core.need(misuseCfg, 'aliases', where .. '.misuse') } } })
core.need(misuseCfg, 'warning', where .. '.misuse')
core.need(misuseCfg, 'maintenance', where .. '.misuse')
local function aliasOverride(compiled)
return function(value)
return core.parseAliasSetOverride(value, compiled)
end
end
local function typeOverride(value)
return core.parseExactOverride(value, { 'variant', 'subvariant' })
end
local function variantCanon(value)
local found, valid = core.parseAliasSetOverride(value, variants)
return valid and found[1] or value
end
local function maint(dim, key)
return dim.maintenance and dim.maintenance[key]
end
function p._categories(args)
local out = core.newList()
local function has(field)
return core.trim(args[field]) ~= ''
end
local function emit(pattern, state)
for _, value in ipairs(state.values) do
out:add(core.fill(pattern, value))
end
end
local typeMatches = core.matchAliasSets(args.type, types)
local misused = #core.matchAliasSets(args.type, misuse) > 0
if misused then
mw.addWarning(misuseCfg.warning)
out:add(misuseCfg.maintenance)
end
local detectedType = 'variant'
for _, value in ipairs(typeMatches) do
if value == 'subvariant' then detectedType = 'subvariant' end
end
local kind = core.resolve {
candidates = { detectedType },
override = args.category_type,
parse = typeOverride,
max = 1,
}
local classification = kind.values[1]
if kind.source ~= 'override' and has('type') and #typeMatches == 0 and not misused then
out:add(maint(D.type, 'unrecognized'))
end
local year = core.resolve {
candidates = core.findYears(args.origin),
override = args.category_year,
parse = core.parseYearOverride,
suppress = args.noyearcat, -- backward compatability
max = 1,
}
if year.source ~= 'suppressed' then
if year.values[1] then
out:add(core.fill(D.year.name[classification], year.values[1]))
else
out:add(D.year.undated[classification])
end
if year.overflow then out:add(maint(D.year, 'overflow')) end
end
local site = core.resolve {
candidates = core.matchAliasSets(args.origin, sites),
override = args.category_site,
parse = aliasOverride(sites),
canon = siteBucket,
}
emit(D.site.name[classification], site)
if site.source == 'detected' and site.undetected then out:add(maint(D.site, 'unrecognized')) end
local parent = core.resolve {
candidates = core.findLinkTargets(args.parent),
override = args.category_parent,
parse = core.parseLinkTargetOverride,
canon = variantCanon,
applicable = classification == 'subvariant',
suppress = args.noparentcat,
max = 3,
}
if parent.source == 'detected' or parent.source == 'override' then
emit(D.parent.name, parent)
if #parent.values == 0 then out:add(maint(D.parent, 'undetected')) end
if parent.overflow then out:add(maint(D.parent, 'overflow')) end
end
if classification == 'variant' then
local counts = core.readDataAttr(args.booru_posts, 'boorusearch-count')
if #counts == 0 then
out:add(maint(D.posts, 'missing'))
else
out:add(core.pickBand(core.parseNumber(counts[1]), D.posts.bands))
end
end
if core.anyCustomized(kind, year, site, parent) then out:add(usingOverrideCat) end
if core.anyInvalid(kind, year, site, parent) then out:add(invalidOverrideCat) end
return out
end
p.main = core.makeMain(p._categories, config.namespaces)
return p
