Initial commit

master
LoneWolfHT 2021-01-26 15:15:46 -08:00
parent 739404613f
commit 4b78efd897
6 changed files with 381 additions and 260 deletions

4
.gitignore vendored
View File

@ -1 +1,3 @@
./output.txt
target/
.vscode/
Cargo.lock

11
Cargo.toml Normal file
View File

@ -0,0 +1,11 @@
[package]
name = "mtf-modpost-gen"
version = "0.2.0"
authors = ["LoneWolfHT <lonewolf04361@gmail.com>"]
edition = "2018"
[dependencies]
clipboard = "0.5.0"
term = "0.7.0"
curl = "0.4.34"
json = "0.12.4"

230
MMG.lua
View File

@ -1,230 +0,0 @@
local info = {
techmodname = "",
modname = "",
shortdescription = "",
repository = "",
contentdb = "",
license_code = "",
license_media = "",
depends = "",
optional_depends = "",
type = "",
game = "",
game_link = "",
download = "",
screenshots = {}
}
local answer
local function cprint(...)
io.write(...)
end
local function getinput(type)
local temp
if type == "line" then
cprint("\n\n> ")
temp = io.read("*line")
elseif type == "lines" then
cprint("\n\nMultiline input-> ")
local lines = ""
temp = io.read("*line")
while temp ~= "" do
lines = lines .. temp .. "\n"
temp = io.read("*line")
end
temp = lines:sub(1, -2)
end
cprint("\n\n")
return temp
end
local function string_to_table(string)
local temptable = {}
while string:find("\n") ~= nil do
table.insert(temptable, string:sub(1, string:find("\n")-1))
string = string:sub(string:find("\n") + 1)
end
if string ~= "" then
table.insert(temptable, string)
end
return temptable
end
cprint("\n[Minetest Modpost Gen]\nPress enter twice with multiline input to move to the next question\n\n")
-- Get info needed
while info.techmodname == "" do
cprint("Please enter the name for your mod as defined in your mod.conf")
info.techmodname = getinput("line")
end
cprint("Please enter the user-friendly name for your mod (Skip to use capitalized mod.conf name)")
info.modname = getinput("line")
if info.modname == "" then info.modname = info.techmodname:sub(1, 1):upper() .. info.techmodname:sub(2) end
cprint("Please enter a short description (Skip for none)")
info.shortdescription = getinput("lines")
cprint("Do you have screenshots to provide? Answer yes/no")
repeat
answer = getinput("line"):lower()
until answer:find("y") ~= nil or answer:find("n") ~= nil
if answer:find("y") ~= nil then -- answer was yes
cprint("Please provide link(s) to your screenshot(s). One per line")
info.screenshots = string_to_table(getinput("lines"))
end
cprint("Please enter the link to your Github repo (Skip for none)")
info.repository = getinput("line")
if info.repository ~= "" and info.repository:sub(-1, -1) ~= "/" then
info.repository = info.repository .. "/"
end
cprint("Please enter the link to your CDB page")
info.contentdb = getinput("line")
if info.contentdb == "" then -- No CDB link provided, so can't create a download link out of it
while info.download == "" do
cprint("Please provide a download link")
info.download = getinput("line")
end
else
if info.contentdb:sub(-1, -1) ~= "/" then
info.contentdb = info.contentdb .. "/"
end
end
while info.license_code == "" do
cprint("Please enter the license of your code")
info.license_code = getinput("line")
end
cprint("Please enter the license of your media (skip if your mod has no media)")
info.license_media = getinput("line")
cprint("Please enter the dependencies of your mod (skip for none)")
info.depends = getinput("line")
cprint("Please list the optional dependencies of your mod (skip for none)")
info.optional_depends = getinput("line")
if info.depends == "" then info.depends = "None" end
if info.optional_depends == "" then info.optional_depends = "None" end
-- Prepare info for generation
cprint("Is your mod WIP? Answer yes/no")
repeat
answer = getinput("line"):lower()
until answer:find("y") ~= nil or answer:find("n") ~= nil
if answer:find("y") ~= nil then
info.type = "WIP"
else
info.type = "Mod"
end
cprint("Is your mod made only for a specific game? Type the name of it if so. Skip if not")
info.game = getinput("line")
if info.game ~= "" then
cprint("Please provide a link to the game, if there is one")
info.game_link = getinput("line")
end
--
--- Generate mod topic
--
local file = assert(io.open("output.txt", "w"))
-- Subject
file:write("Subject:\n")
file:write(("-\n[%s] %s [%s]\n-\n"):format(info.type, info.modname, info.techmodname))
-- Content
file:write("\nContent:\n-\n")
file:write(("[size=150][b]%s[/b][/size]\n\n"):format(info.modname))
if info.shortdescription ~= "" then
file:write(info.shortdescription)
end
if info.screenshots ~= {} and #info.screenshots >= 1 then
if #info.screenshots == 1 then
file:write(("\n\n[img]%s[/img]"):format(info.screenshots[1]))
else
file:write("\n\n[spoiler=Screenshots]\n")
for _, imglink in ipairs(info.screenshots) do
file:write(("[img]%s[/img]\n"):format(imglink))
end
file:write("[/spoiler]")
end
end
local download_link
if info.contentdb == "" then
download_link = info.download
else
download_link = info.contentdb .. "download/"
end
file:write(("\n\n[b]Downloads:[/b] [url=%s]Latest Stable[/url]\n\n"):format(download_link))
if info.contentdb ~= "" then
file:write(("[b]View:[/b] [url=%s]On ContentDB[/url]\n"):format(info.contentdb))
end
if info.repository ~= "" then
file:write(("[b]View:[/b] [url=%s]Source Code[/url]\n"):format(info.repository))
end
file:write("\n")
if info.license_media == "" then
file:write(("[b]License:[/b] %s\n"):format(info.license_code))
else
file:write(("[b]License of Code:[/b] %s\n"):format(info.license_code))
file:write(("[b]License of Media:[/b] %s\n"):format(info.license_media))
end
if info.game == "" then
file:write(("\n[b]Depends:[/b] %s\n"):format(info.depends))
file:write(("[b]Optional Depends:[/b] %s"):format(info.optional_depends))
else
if info.game_link == "" then
file:write(("\n[b]Depends upon game:[/b] %s"):format(info.game))
else
file:write(("\n[b]Depends upon game:[/b] [url=%s]%s[/url]"):format(info.game_link, info.game))
end
end
file:write("\n-\n")
file:close()
cprint("Modpost generated! If you want to include a full description put it at the end of the post.\n")

View File

@ -2,33 +2,7 @@
A CLI-based forum topic generator I use for easily creating consistent mod posts for all of my mods on the Minetest Forums
## Example output
There is currently a question-based and cdb-pulling method for generation.
```
Subject:
-
[Mod] 3d Armor form for mobile users [3d_armor_mobile]
-
Content:
-
[size=150][b]3d Armor form for mobile users[/b][/size]
Adds a command to bring up a formspec that allows mobile users to put on armor
[img]https://github.com/LoneWolfHT/3d_armor_mobile/blob/master/screenshot.png[/img]
[b]Downloads:[/b] [url=https://content.minetest.net/packages/Lone_Wolf/3d_armor_mobile/download/]Latest Stable[/url]
[b]View:[/b] [url=https://content.minetest.net/packages/Lone_Wolf/3d_armor_mobile/]On ContentDB[/url]
[b]View:[/b] [url=https://github.com/LoneWolfHT/3d_armor_mobile/]Source Code[/url]
[b]License:[/b] MIT
[b]Depends:[/b] 3d_armor
[b]Optional Depends:[/b] None
-
```
Screenshot of my terminal when generating it:
https://cdn.discordapp.com/attachments/453772264542961666/676247448716443648/unknown.png
Screenshot of my terminal when generating a mod post:
![Screenshot](screenshot.png)

BIN
screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

364
src/main.rs Normal file
View File

@ -0,0 +1,364 @@
use std::env;
use std::io;
extern crate term;
use std::io::prelude::*;
use clipboard::ClipboardContext;
use clipboard::ClipboardProvider;
use curl::easy::{Easy, List};
const SCREENSHOT_LIMIT: usize = 5;
#[derive(Debug)]
struct ModInfo {
modtype: String,
name: String,
author: String,
desc: String,
license: String,
media_license: String,
repo: String,
screenshots: [String; SCREENSHOT_LIMIT],
title: String,
download_link: String,
depends: String,
optional_depends: String,
contentdb: String,
}
fn main() {
let args: Vec<String> = env::args().collect();
let mut ctx: ClipboardContext = ClipboardProvider::new().unwrap();
let mut result: String = String::new();
let mut input: String = String::new();
let mut modinfo = ModInfo {
modtype: String::from("Mod"),
name: String::new(),
title: String::new(),
author: String::new(),
desc: String::new(),
license: String::new(),
media_license: String::new(),
repo: String::new(),
screenshots: Default::default(),
download_link: String::new(),
depends: String::new(),
optional_depends: String::new(),
contentdb: String::new(),
};
print_color("[MT Forums Modpost Generator]\n\n", term::color::GREEN);
if args.len() > 1 {
if args[1] == "--fromcdb" || args[1] == "-c" {
if args.len() <= 2 {
get_input(true, "Please link your ContentDB page", &mut input);
} else {
input = args[2].clone();
}
let mut link: String = input.replace(
"https://content.minetest.net/",
"https://content.minetest.net/api/",
);
if input == link {
print_color("Invalid link!\n\n", term::color::RED);
return;
} else {
let result = get_info_from_cdb(&link);
modinfo.name.push_str(result["name"].as_str().unwrap());
modinfo.title.push_str(result["title"].as_str().unwrap());
modinfo.author.push_str(result["author"].as_str().unwrap());
modinfo
.desc
.push_str(result["short_description"].as_str().unwrap());
modinfo
.license
.push_str(result["license"].as_str().unwrap());
modinfo
.media_license
.push_str(result["media_license"].as_str().unwrap());
modinfo.repo.push_str(result["repo"].as_str().unwrap());
for (idx, val) in result["screenshots"].members().enumerate() {
if idx < SCREENSHOT_LIMIT {
modinfo.screenshots[idx].push_str(val.as_str().unwrap());
} else {
break;
}
}
modinfo.download_link = link.replace("api/", "");
// download link is cdb link without /download on the end
modinfo.contentdb = modinfo.download_link.clone();
modinfo.download_link.push_str("download/");
link.push_str("dependencies/");
let result = get_info_from_cdb(&link);
for dep in result[modinfo.author.clone() + "/" + &modinfo.name].members() {
let mut dependptr = &mut modinfo.depends;
if dep["is_optional"] == true {
dependptr = &mut modinfo.optional_depends;
}
if !dependptr.is_empty() {
dependptr.push_str(", ");
}
dependptr.push_str(dep["name"].as_str().unwrap())
}
}
} else {
println!("Usage: {} [--fromcdb | -c]", args[0]);
return;
}
} else {
print_color("To skip a request just press enter\n", term::color::YELLOW);
get_input(
true,
"Please provide the mod name used in your mod.conf",
&mut modinfo.name,
);
get_input(
true,
"Please provide a human-readable mod name",
&mut modinfo.title,
);
get_input(
true,
"Please provide a one-line mod description",
&mut modinfo.desc,
);
get_input(true, "Please provide a code license", &mut modinfo.license);
get_input(
false,
"Please provide a media license",
&mut modinfo.media_license,
);
get_input(
false,
"Please provide a link to your git repo",
&mut modinfo.repo,
);
for idx in 0..SCREENSHOT_LIMIT {
if !get_input(
false,
"Please link a screenshot",
&mut modinfo.screenshots[idx],
) {
break;
}
}
get_input(
false,
"Please provide a direct download link",
&mut modinfo.download_link,
);
get_input(
false,
"Please list your hard dependencies",
&mut modinfo.depends,
);
get_input(
false,
"Please list your optional dependencies",
&mut modinfo.optional_depends,
);
get_input(false, "Is your mod a Work In Progress? (y/n)", &mut input);
if input.contains("y") {
modinfo.modtype = String::from("WIP")
}
}
print_color("[!Forum Post Generated!]\n\n", term::color::GREEN);
// Give forum post title
result.push_str(&format!(
"[{}] {} [{}]",
modinfo.modtype, modinfo.title, modinfo.name
));
if get_yn_answer("Do you want to copy the mod post title to your clipboard? (y/n)") {
ctx.set_contents(result.to_owned()).unwrap();
println!("Copied to clipboard!\n")
}
print_color(
&format!("\n{}\n{}\n{}\n\n", "-".repeat(8), result, "-".repeat(8)),
term::color::YELLOW,
);
get_yn_answer("Press Enter to continue"); // you see nothing
result.clear();
get_result(modinfo, &mut result);
if get_yn_answer("Do you want to copy the mod post contents to your clipboard? (y/n)") {
ctx.set_contents(result.to_owned()).unwrap();
println!("Copied to clipboard!\n")
}
print_color(
&format!("\n{}\n{}\n{}\n\n", "-".repeat(15), &result, "-".repeat(15),),
term::color::YELLOW,
);
}
fn get_result(modinfo: ModInfo, result: &mut String) {
// Add header
result.push_str(&format!("[h]{}[/h]\n{}\n\n", modinfo.title, modinfo.desc));
// Show one screenshot and put the rest in a spoiler
if !modinfo.screenshots[0].is_empty() {
result.push_str(&format!("[img]{}[/img]\n", modinfo.screenshots[0]));
if !modinfo.screenshots[1].is_empty() {
result.push_str("[tspoiler=More Screenshots]");
for idx in 1..SCREENSHOT_LIMIT {
if modinfo.screenshots[idx].is_empty() {
break;
}
result.push_str(&format!("\n[img]{}[/img]", modinfo.screenshots[idx]));
}
result.push_str("\n[/tspoiler]\n\n")
} else {
result.push('\n');
}
}
result.push_str("[h][/h]\n\n");
// Add direct download link
if !modinfo.download_link.is_empty() {
result.push_str(&format!(
"[b]Downloads:[/b] [url={}]Latest Stable[/url]\n",
modinfo.download_link
));
}
// Add repo link
if !modinfo.repo.is_empty() {
result.push_str(&format!(
"[b]View:[/b] [url={}]Source Code[/url]",
modinfo.repo
));
if !modinfo.contentdb.is_empty() {
result.push_str(&format!(" | [url={}]ContentDB[/url]", modinfo.contentdb));
}
result.push_str("\n\n");
} else {
result.push('\n');
}
// Add mod license(s)
if modinfo.media_license.is_empty() {
result.push_str(&format!("[b]License:[/b] {}\n\n", modinfo.license));
} else {
result.push_str(&format!("[b]License of Code:[/b] {}\n", modinfo.license));
result.push_str(&format!(
"[b]License of Media:[/b] {}\n\n",
modinfo.media_license
));
}
// Add mod dependencies
if !modinfo.depends.is_empty() {
result.push_str(&format!("[b]Depends:[/b] {}\n", modinfo.depends));
}
if !modinfo.optional_depends.is_empty() {
result.push_str(&format!(
"[b]Optional Depends:[/b] {}\n",
modinfo.optional_depends
));
}
}
fn get_input(required: bool, message: &str, string: &mut String) -> bool {
print!(
"\n[{}] {}\n\n",
if required { "Required" } else { "Optional" },
message
);
while string.is_empty() {
io::stdin().read_line(string).expect("Failed to read");
*string = String::from(string.trim());
if !required {
break;
}
}
string.shrink_to_fit();
return !string.is_empty();
}
// Get y/n answer
fn get_yn_answer(message: &str) -> bool {
let mut string: String = String::new();
println!("{}\n", message);
io::stdin().read_line(&mut string).expect("Failed to read");
string = String::from(string.trim());
return string.contains("y");
}
fn print_color(message: &str, color: term::color::Color) {
let mut t = term::stdout().unwrap();
t.fg(color).unwrap();
write!(t, "{}", message).unwrap();
t.reset().unwrap();
}
fn get_info_from_cdb(link: &String) -> json::JsonValue {
let mut easy = Easy::new();
let mut list = List::new();
let mut json_buf = Vec::new();
easy.url(&link).unwrap();
list.append("Content-Type: application/json").unwrap();
easy.http_headers(list).unwrap();
{
let mut transfer = easy.transfer();
transfer
.write_function(|data| {
json_buf.extend_from_slice(data);
Ok(data.len())
})
.unwrap();
transfer.perform().unwrap();
}
return json::parse(std::str::from_utf8(json_buf.as_slice()).expect("Invalid UTF-8")).unwrap();
}