Tom
2022-02-06 21:21:34 +01:00
committed by GitHub
parent 140e0ef399
commit b3f03c7c14
20 changed files with 1487 additions and 4 deletions
+49
View File
@@ -0,0 +1,49 @@
name: Autowiki
on:
schedule:
- cron: "5 4 * * *"
workflow_dispatch:
jobs:
autowiki:
runs-on: ubuntu-latest
steps:
- name: "Check for AUTOWIKI_USERNAME"
id: secrets_set
env:
ENABLER_SECRET: ${{ secrets.AUTOWIKI_USERNAME }}
run: |
unset SECRET_EXISTS
if [ -n "$ENABLER_SECRET" ]; then SECRET_EXISTS=true ; fi
echo "::set-output name=SECRETS_ENABLED::$SECRET_EXISTS"
- name: Checkout
if: steps.secrets_set.outputs.SECRETS_ENABLED
uses: actions/checkout@v2
- name: Restore BYOND cache
if: steps.secrets_set.outputs.SECRETS_ENABLED
uses: actions/cache@v2
with:
path: ~/BYOND
key: ${{ runner.os }}-byond-${{ secrets.CACHE_PURGE_KEY }}
- name: Install rust-g
if: steps.secrets_set.outputs.SECRETS_ENABLED
run: |
sudo dpkg --add-architecture i386
sudo apt update || true
sudo apt install -o APT::Immediate-Configure=false libssl1.1:i386
bash tools/ci/install_rust_g.sh
- name: Compile and generate Autowiki files
if: steps.secrets_set.outputs.SECRETS_ENABLED
run: |
bash tools/ci/install_byond.sh
source $HOME/BYOND/byond/bin/byondsetup
tools/build/build --ci autowiki
- name: Run Autowiki
if: steps.secrets_set.outputs.SECRETS_ENABLED
env:
USERNAME: ${{ secrets.AUTOWIKI_USERNAME }}
PASSWORD: ${{ secrets.AUTOWIKI_PASSWORD }}
run: |
cd tools/autowiki
npm install
cd ../..
node tools/autowiki/autowiki.js data/autowiki_edits.txt data/autowiki_files/
+3
View File
@@ -197,5 +197,8 @@ Temporary Items
/tools/LinuxOneShot/TGS_Instances
/tools/LinuxOneShot/TGS_Logs
# Autowiki
/tools/autowiki/node_modules
# TGUI bundles - SKYRAT EDIT
/tgui/public/tgui-common.bundle.js
-1
View File
@@ -391,7 +391,6 @@
/proc/sort_names(list/list_to_sort, order=1)
return sortTim(list_to_sort.Copy(), order >= 0 ? /proc/cmp_name_asc : /proc/cmp_name_dsc)
///Converts a bitfield to a list of numbers (or words if a wordlist is provided)
/proc/bitfield_to_list(bitfield = 0, list/wordlist)
var/list/return_list = list()
+4
View File
@@ -51,6 +51,10 @@
//#define UNIT_TESTS //If this is uncommented, we do a single run though of the game setup and tear down process with unit tests in between
/// If this is uncommented, Autowiki will generate edits and shut down the server.
/// Prefer the autowiki build target instead.
// #define AUTOWIKI
#ifndef PRELOAD_RSC //set to:
#define PRELOAD_RSC 2 // 0 to allow using external resources or on-demand behaviour;
#endif // 1 to use the default behaviour;
@@ -15,7 +15,8 @@ PROCESSING_SUBSYSTEM_DEF(station)
/datum/controller/subsystem/processing/station/Initialize(timeofday)
//If doing unit tests we don't do none of that trait shit ya know?
#ifndef UNIT_TESTS
// Autowiki also wants consistent outputs, for example making sure the vending machine page always reports the normal products
#if !defined(UNIT_TESTS) && !defined(AUTOWIKI)
SetupTraits()
#endif
+4
View File
@@ -85,6 +85,10 @@ GLOBAL_VAR(restart_counter)
HandleTestRun()
#endif
#ifdef AUTOWIKI
setup_autowiki()
#endif
/world/proc/InitTgs()
TgsNew(new /datum/tgs_event_handler/impl, TGS_SECURITY_TRUSTED)
GLOB.revdata.load_tgs_info()
+33
View File
@@ -0,0 +1,33 @@
/// When the `AUTOWIKI` define is enabled, will generate an output file for tools/autowiki/autowiki.js to consume.
/// Autowiki code intentionally still *exists* even without the define, to ensure developers notice
/// when they break it immediately, rather than until CI or worse, call time.
#if defined(AUTOWIKI) || defined(UNIT_TESTS)
/proc/setup_autowiki()
Master.sleep_offline_after_initializations = FALSE
SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, /proc/generate_autowiki))
SSticker.start_immediately = TRUE
CONFIG_SET(number/round_end_countdown, 0)
/proc/generate_autowiki()
var/output = generate_autowiki_output()
rustg_file_write(output, "data/autowiki_edits.txt")
qdel(world)
#endif
/// Returns a string of the autowiki output file
/proc/generate_autowiki_output()
var/total_output = ""
for (var/datum/autowiki/autowiki_type as anything in subtypesof(/datum/autowiki))
var/datum/autowiki/autowiki = new autowiki_type
var/output = autowiki.generate()
if (!istext(output))
CRASH("[autowiki_type] does not generate a proper output!")
total_output += json_encode(list(
"title" = autowiki.page,
"text" = output,
)) + "\n"
return total_output
+54
View File
@@ -0,0 +1,54 @@
/// A representation of an automated wiki page.
/datum/autowiki
/// The page on the wiki to be replaced.
/// This should never be a user-facing page, like "Guide to circuits".
/// It should always be a template that only Autowiki should touch.
/// For example: "Template:Autowiki/CircuitInfo".
var/page
/// Override and return the new text of the page.
/// This proc can be impure, usually to call `upload_file`.
/datum/autowiki/proc/generate()
SHOULD_CALL_PARENT(FALSE)
CRASH("[type] does not implement generate()!")
/// Generates an auto formatted template user.
/// Your autowiki should ideally be a *lot* of these.
/// It lets wiki editors edit it much easier later, without having to enter repo.
/// Parameters will be passed in by name. That means your template should expect
/// something that looks like `{{ Autowiki_Circuit|name=Combiner|description=This combines }}`
/// Lists, which must be array-like (no keys), will be turned into a flat list with their key and a number,
/// such that list("food" = list("fruit", "candy")) -> food1=fruit|food2=candy
/datum/autowiki/proc/include_template(name, parameters)
var/template_text = "{{[name]"
var/list/prepared_parameters = list()
for (var/key in parameters)
var/value = parameters[key]
if (islist(value))
for (var/index in 1 to length(value))
prepared_parameters["[key][index]"] = "[value[index]]"
else
prepared_parameters[key] = value
for (var/parameter_name in prepared_parameters)
template_text += "|[parameter_name]="
template_text += "[prepared_parameters[parameter_name]]"
template_text += "}}"
return template_text
/// Takes an icon and uploads it to Autowiki-name.png.
/// Do your best to make sure this is unique, so it doesn't clash with other autowiki icons.
/datum/autowiki/proc/upload_icon(icon/icon, name)
// Fuck you
if (IsAdminAdvancedProcCall())
return
fcopy(icon, "data/autowiki_files/[name].png")
/// Escape a parameter such that it can be correctly put inside a wiki output
/datum/autowiki/proc/escape_value(parameter)
// | is a special character in MediaWiki, and must be escaped by...using another template.
return replacetextEx(parameter, "|", "{{!}}")
+68
View File
@@ -0,0 +1,68 @@
/datum/autowiki/techweb
page = "Template:Autowiki/Content/Techweb"
/datum/autowiki/techweb/generate()
var/output = ""
for (var/node_id in sort_list(SSresearch.techweb_nodes, /proc/sort_research_nodes))
var/datum/techweb_node/node = SSresearch.techweb_nodes[node_id]
if (!node.show_on_wiki)
continue
if (!valid_node(node))
continue
output += "\n\n" + include_template("Autowiki/TechwebEntry", list(
"name" = escape_value(node.display_name),
"description" = escape_value(node.description),
"prerequisites" = generate_prerequisites(node.prereq_ids),
"designs" = generate_designs(node.design_ids),
))
return output
/datum/autowiki/techweb/proc/valid_node(datum/techweb_node/node)
return !node.experimental
/datum/autowiki/techweb/proc/generate_designs(list/design_ids)
var/output = ""
for (var/design_id in design_ids)
var/datum/design/design = SSresearch.techweb_designs[design_id]
output += include_template("Autowiki/TechwebEntryDesign", list(
"name" = escape_value(design.name),
"description" = escape_value(design.get_description()),
))
return output
/datum/autowiki/techweb/proc/generate_prerequisites(list/prereq_ids)
var/output = ""
for (var/prereq_id in prereq_ids)
var/datum/techweb_node/node = SSresearch.techweb_nodes[prereq_id]
output += include_template("Autowiki/TechwebEntryPrerequisite", list(
"name" = escape_value(node.display_name),
))
return output
/datum/autowiki/techweb/experimental
page = "Template:Autowiki/Content/Techweb/Experimental"
/datum/autowiki/techweb/experimental/valid_node(datum/techweb_node/node)
return node.experimental
/proc/sort_research_nodes(node_id_a, node_id_b)
var/datum/techweb_node/node_a = SSresearch.techweb_nodes[node_id_a]
var/datum/techweb_node/node_b = SSresearch.techweb_nodes[node_id_b]
var/prereq_difference = node_a.prereq_ids.len - node_b.prereq_ids.len
if (prereq_difference != 0)
return prereq_difference
var/experiment_difference = node_a.required_experiments.len - node_b.required_experiments.len
if (experiment_difference != 0)
return experiment_difference
return sorttext(node_b.display_name, node_a.display_name)
+56
View File
@@ -0,0 +1,56 @@
/datum/autowiki/vending
page = "Template:Autowiki/Content/VendingMachines"
/datum/autowiki/vending/generate()
var/output = ""
var/list/cached_products = list()
// `powered()` checks if its in a null loc to say it's not powered.
// So we put it inside, something
var/obj/parent = new
for (var/vending_type in sort_list(subtypesof(/obj/machinery/vending), /proc/cmp_typepaths_asc))
var/obj/machinery/vending/vending_machine = new vending_type(parent)
vending_machine.use_power = FALSE
vending_machine.update_icon(UPDATE_ICON_STATE)
// Technically won't match if product amounts change, but this isn't likely
var/products_cache_key = vending_machine.products.Join("-") + "&" + vending_machine.contraband.Join("-") + "&" + vending_machine.premium.Join("-")
// In the future, this should show all vending machines that have the same products
if (products_cache_key in cached_products)
qdel(vending_machine)
continue
cached_products += products_cache_key
var/filename = SANITIZE_FILENAME(escape_value(format_text(vending_machine.name)))
output += include_template("Autowiki/VendingMachine", list(
"icon" = escape_value(filename),
"name" = escape_value(format_text(vending_machine.name)),
"products" = format_product_list(vending_machine.products),
"contraband" = format_product_list(vending_machine.contraband),
"premium" = format_product_list(vending_machine.premium),
))
// It would be cool to make this support gifs someday, but not now
upload_icon(getFlatIcon(vending_machine, no_anim = TRUE), filename)
qdel(vending_machine)
qdel(parent)
return output
/datum/autowiki/vending/proc/format_product_list(list/product_list)
var/output = ""
for (var/obj/product_path as anything in product_list)
output += include_template("Autowiki/VendingMachineProduct", list(
"name" = escape_value(capitalize(format_text(initial(product_path.name)))),
"amount" = product_list[product_path],
))
return output
+7 -1
View File
@@ -24,7 +24,7 @@ other types of metals and chemistry for reagents).
/// Name of the created object
var/name = "Name"
/// Description of the created object
var/desc = "Desc"
var/desc = null
/// The ID of the design. Used for quick reference. Alphanumeric, lower-case, no symbols
var/id = DESIGN_ID_IGNORE
/// Bitflags indicating what machines this design is compatable with. ([IMPRINTER]|[AWAY_IMPRINTER]|[PROTOLATHE]|[AWAY_LATHE]|[AUTOLATHE]|[MECHFAB]|[BIOGENERATOR]|[LIMBGROWER]|[SMELTER])
@@ -86,6 +86,12 @@ other types of metals and chemistry for reagents).
sheet.send(user)
return sheet.icon_tag(id)
/// Returns the description of the design
/datum/design/proc/get_description()
var/obj/object_build_item_path = build_path
return isnull(desc) ? initial(object_build_item_path.desc) : desc
////////////////////////////////////////
//Disks for transporting design datums//
////////////////////////////////////////
@@ -36,11 +36,14 @@
var/list/required_experiments = list()
/// If completed, these experiments give a specific point amount discount to the node.area
var/list/discount_experiments = list()
/// Whether or not this node should show on the wiki
var/show_on_wiki = TRUE
/datum/techweb_node/error_node
id = "ERROR"
display_name = "ERROR"
description = "This usually means something in the database has corrupted. If it doesn't go away automatically, inform Central Command for their techs to fix it ASAP(tm)"
show_on_wiki = FALSE
/datum/techweb_node/proc/Initialize()
//Make lists associative for lookup
+1
View File
@@ -46,6 +46,7 @@
#include "achievements.dm"
#include "anchored_mobs.dm"
#include "anonymous_themes.dm"
#include "autowiki.dm"
#include "bespoke_id.dm"
#include "binary_insert.dm"
#include "bloody_footprints.dm"
+35
View File
@@ -0,0 +1,35 @@
/// Tests that all autowikis generate something without runtiming
/datum/unit_test/autowiki
/datum/unit_test/autowiki/Run()
TEST_ASSERT(istext(generate_autowiki_output()), "generate_autowiki_output() did not finish successfully!")
/// Test that `include_template` produces reasonable results
/datum/unit_test/autowiki_include_template
/datum/unit_test/autowiki_include_template/Run()
var/datum/autowiki/autowiki_api = new
TEST_ASSERT_EQUAL( \
autowiki_api.include_template("Template"), \
"{{Template}}", \
"Basic template did not format correctly" \
)
TEST_ASSERT_EQUAL( \
autowiki_api.include_template("Template", list("name" = "Mothblocks")), \
"{{Template|name=Mothblocks}}", \
"Template with basic arguments did not format correctly" \
)
TEST_ASSERT_EQUAL( \
autowiki_api.include_template("Template", list("name" = autowiki_api.escape_value("P|peline"))), \
"{{Template|name=P{{!}}peline}}", \
"Template with escaped arguments did not format correctly" \
)
TEST_ASSERT_EQUAL( \
autowiki_api.include_template("Template", list("food" = list("fruit", "candy"))), \
"{{Template|food1=fruit|food2=candy}}", \
"Template with array arguments did not format correctly" \
)
@@ -197,7 +197,7 @@
var/list/part = list(
"name" = D.name,
"desc" = D.desc == "Desc" ? initial(built_item.desc) : D.desc,
"desc" = D.get_description(),
"printTime" = get_construction_time_w_coeff(initial(D.construction_time))/10,
"cost" = cost,
"id" = D.id,
+4
View File
@@ -2236,6 +2236,10 @@
#include "code\modules\atmospherics\machinery\portable\portable_atmospherics.dm"
#include "code\modules\atmospherics\machinery\portable\pump.dm"
#include "code\modules\atmospherics\machinery\portable\scrubber.dm"
#include "code\modules\autowiki\autowiki.dm"
#include "code\modules\autowiki\pages\base.dm"
#include "code\modules\autowiki\pages\techweb.dm"
#include "code\modules\autowiki\pages\vending.dm"
#include "code\modules\awaymissions\away_props.dm"
#include "code\modules\awaymissions\bluespaceartillery.dm"
#include "code\modules\awaymissions\cordon.dm"
+83
View File
@@ -0,0 +1,83 @@
const fs = require("fs").promises
const MWBot = require("mwbot")
const { USERNAME, PASSWORD } = process.env
if (!USERNAME) {
console.error("USERNAME was not set.")
process.exit(1)
}
if (!PASSWORD) {
console.error("PASSWORD was not set.")
process.exit(1)
}
const PAGE_EDIT_FILENAME = process.argv[2]
if (!PAGE_EDIT_FILENAME) {
console.error("No filename specified to edit pages")
process.exit(1)
}
const FILE_EDIT_FILENAME = process.argv[3]
if (!FILE_EDIT_FILENAME) {
console.error("No filename specified to edit files")
process.exit(1)
}
async function main() {
console.log(`Reading from ${PAGE_EDIT_FILENAME}`)
const editFile = await (await fs.readFile(PAGE_EDIT_FILENAME, "utf8")).split("\n")
console.log(`Logging in as ${USERNAME}`)
const bot = new MWBot()
await bot.loginGetEditToken({
apiUrl: "https://tgstation13.org/wiki/api.php",
username: USERNAME,
password: PASSWORD,
})
console.log("Logged in")
// This is not Promise.all as to not flood with a bunch of traffic at once
for (const editLine of editFile) {
if (editLine.length === 0) {
continue
}
let { title, text } = JSON.parse(editLine)
text = "<noinclude><b>This page is automated by Autowiki. Do NOT edit it manually.</b></noinclude>" + text
console.log(`Editing ${title}...`)
await bot.edit(
title,
text,
`Autowiki edit @ ${ new Date().toISOString() }`,
)
}
// Same here
for (const asset of await fs.readdir(FILE_EDIT_FILENAME)) {
const assetPath = `${FILE_EDIT_FILENAME}/${asset}`
const assetName = `Autowiki-${asset}`
console.log(`Replacing ${assetName}...`)
await bot.upload(
assetName,
assetPath,
`Autowiki upload @ ${ new Date().toISOString() }`,
).catch(error => {
if (error.code === "fileexists-no-change") {
console.log(`${assetName} is an exact duplicate`)
} else {
return Promise.reject(error)
}
})
}
}
main().catch(console.error)
+1040
View File
File diff suppressed because it is too large Load Diff
+10
View File
@@ -0,0 +1,10 @@
{
"name": "autowiki",
"version": "1.0.0",
"description": "Automatically publish generated pages to the tg wiki",
"main": "autowiki.js",
"author": "Mothblocks",
"dependencies": {
"mwbot": "^2.0.0"
}
}
+30
View File
@@ -113,6 +113,36 @@ export const DmTestTarget = new Juke.Target({
},
});
export const AutowikiTarget = new Juke.Target({
parameters: [DefineParameter],
dependsOn: ({ get }) => [
get(DefineParameter).includes('ALL_MAPS') && DmMapsIncludeTarget,
],
outputs: [
'data/autowiki_edits.txt',
],
executes: async ({ get }) => {
fs.copyFileSync(`${DME_NAME}.dme`, `${DME_NAME}.test.dme`);
await DreamMaker(`${DME_NAME}.test.dme`, {
defines: ['CBT', 'AUTOWIKI', ...get(DefineParameter)],
warningsAsErrors: get(WarningParameter).includes('error'),
});
Juke.rm('data/autowiki_edits.txt');
Juke.rm('data/autowiki_files', { recursive: true });
Juke.rm('data/logs/ci', { recursive: true });
await DreamDaemon(
`${DME_NAME}.test.dmb`,
'-close', '-trusted', '-verbose',
'-params', 'log-directory=ci',
);
Juke.rm('*.test.*');
if (!fs.existsSync('data/autowiki_edits.txt')) {
Juke.logger.error('Autowiki did not generate an output, exiting');
throw new Juke.ExitCode(1);
}
},
})
export const YarnTarget = new Juke.Target({
parameters: [CiParameter],
inputs: [