Three recipes for network-connected KonsolScript apps - from calling a REST API to scraping web pages to building a LAN chat tool.
Modules: curl plugin · JSON · Konsol
Demonstrates the full request/response cycle against the public JSONPlaceholder demo API - no key required. Covers setting headers, GETting a single resource, iterating a JSON array response, and POSTing a new resource.
Usage
minks http_api_client.ks
Sample output
=== HTTP API Client ===
Fetching post #1 from JSONPlaceholder...
HTTP status: 200
--- Post #1 ---
User ID : 1
Title : sunt aut facere repellat provident occaecati excepturi optio reprehenderit
Body : quia et suscipit...
...
Fetching first 3 comments for post #1...
[1] id labore ex et quam laborum <Eliseo@gardner.biz>
laudantium enim quasi est...
...
Creating a new post via POST...
POST status: 201
Server assigned ID: 101
Done.
Script
// http_api_client.ks - call REST APIs and process JSON responses
// Modules: curl plugin, JSON, Konsol
// Usage: minks http_api_client.ks
//
// Demonstrates the full request/response cycle against the public
// JSONPlaceholder API: set headers, GET a single resource, iterate a list,
// and POST a new resource - no API key required.
#include "curl"
Konsol:Print("=== HTTP API Client ===");
// ── Step 1: Set common request headers ───────────────────────────────────────
// Headers persist across calls in this session until ClearHeaders() is called.
Curl:SetHeader("Accept", "application/json");
Curl:SetHeader("User-Agent", "KonsolScript/1.0");
Curl:SetTimeout(10);
// ── Step 2: GET - fetch a single post ────────────────────────────────────────
// Curl:Get writes the response body into the pre-declared variable (ByRef).
Konsol:Print("Fetching post #1 from JSONPlaceholder...");
Var:String body;
try {
Curl:Get("https://jsonplaceholder.typicode.com/posts/1", body);
} catch (CurlException e) {
Konsol:Print("Request failed: ${e.message}");
Konsol:Exit(1);
}
// Always check the HTTP status before parsing the body.
Var:Number status;
Curl:Status(status);
Konsol:Print("HTTP status: ${status}");
if (status != 200) {
Konsol:Print("Request failed - aborting.");
Konsol:Exit(1);
}
// ── Step 3: Parse the JSON response ──────────────────────────────────────────
// JSON:Parse reads the raw string and builds a document (bare identifier).
// JSON:Get uses dot-notation paths to extract individual fields.
Var:Number postId;
Var:Number userId;
Var:String title;
Var:String postBody;
JSON:Parse(body, postDoc);
JSON:Get("id", postDoc, postId);
JSON:Get("userId", postDoc, userId);
JSON:Get("title", postDoc, title);
JSON:Get("body", postDoc, postBody);
Konsol:Print("--- Post #${postId} ---");
Konsol:Print("User ID : ${userId}");
Konsol:Print("Title : ${title}");
Konsol:Print("Body : ${postBody}");
// ── Step 4: GET - fetch a list and iterate ───────────────────────────────────
// Root JSON arrays are accessed by zero-based numeric index paths.
// e.g. "0.name", "1.email" for the first and second elements.
Konsol:Print("");
Konsol:Print("Fetching first 3 comments for post #1...");
Var:String listBody;
try {
Curl:Get("https://jsonplaceholder.typicode.com/posts/1/comments", listBody);
} catch (CurlException e) {
Konsol:Print("Comments request failed: ${e.message}");
Konsol:Exit(1);
}
Curl:Status(status);
if (status != 200) {
Konsol:Print("Comments request failed.");
Konsol:Exit(1);
}
JSON:Parse(listBody, comments);
// JSONPlaceholder always returns 5 comments per post - iterate the first 3.
Var:String cName;
Var:String cEmail;
Var:String cBody;
for (Number i = 0; i < 3; i++) {
Var:Number num = i + 1;
JSON:Get("${i}.name", comments, cName);
JSON:Get("${i}.email", comments, cEmail);
JSON:Get("${i}.body", comments, cBody);
Konsol:Print(" [${num}] ${cName} <${cEmail}>");
Konsol:Print(" ${cBody}");
Konsol:Print("");
}
// ── Step 5: POST - create a new resource ─────────────────────────────────────
// Switch to a JSON Content-Type header for the POST body.
Konsol:Print("Creating a new post via POST...");
Curl:ClearHeaders();
Curl:SetHeader("Content-Type", "application/json");
Curl:SetHeader("Accept", "application/json");
Var:String payload = """{
"title": "KonsolScript demo",
"body": "Hello from minks!",
"userId": 1
}""";
Var:String postResponse;
try {
Curl:Post("https://jsonplaceholder.typicode.com/posts", payload, postResponse);
} catch (CurlException e) {
Konsol:Print("POST failed: ${e.message}");
Konsol:Exit(1);
}
Curl:Status(status);
Konsol:Print("POST status: ${status}"); // expects 201 Created
if (status == 201) {
JSON:Parse(postResponse, newDoc);
Var:Number newId;
JSON:Get("id", newDoc, newId);
Konsol:Print("Server assigned ID: ${newId}");
}
Konsol:Print("Done.");
Modules: curl plugin · Regex · String · File · List · Konsol
Fetches any public URL, extracts the page <title>, and collects all absolute http:// / https:// links using per-line regex matching. Optionally saves the link list to a text file.
Key patterns:
String:Split(html, "\n", lines) - split the response body into linesRegex:Groups applied per-line (same technique as log_analyzer.ks)List:Push / List:Get to accumulate resultsUsage
minks web_scraper.ks <url> [output.txt]
# examples
minks web_scraper.ks https://example.com
minks web_scraper.ks https://example.com links.txt
Sample output
=== Web Scraper ===
Target: https://example.com
HTTP status: 200
Page size: 47 lines
Page title: Example Domain
External links found: 1
[1] https://www.iana.org/domains/reserved
Done.
Script
// web_scraper.ks - fetch a web page and extract data with patterns
// Modules: curl plugin, Regex, String, File, List, Konsol
// Usage: minks web_scraper.ks <url> [output.txt]
//
// Fetches any public URL, extracts the page <title> and all absolute
// http/https links, prints them to the console, and optionally saves
// the link list to a text file.
#include "curl"
Konsol:Print("=== Web Scraper ===");
// ── Step 1: Read arguments ────────────────────────────────────────────────────
Var:List args;
OS:Args(args);
Var:Number argc;
List:Size(args, argc);
if (argc < 1) {
Konsol:Print("Usage: minks web_scraper.ks <url> [output.txt]");
Konsol:Exit(1);
}
Var:String url;
List:Get(0, args, url);
Var:String outFile = "";
if (argc >= 2) {
List:Get(1, args, outFile);
}
Konsol:Print("Target: ${url}");
// ── Step 2: Fetch the page ───────────────────────────────────────────────────
Curl:SetHeader("User-Agent", "KonsolScript/1.0");
Curl:SetTimeout(15);
Var:String html;
try {
Curl:Get(url, html);
} catch (CurlException e) {
Konsol:Print("Fetch failed: ${e.message}");
Konsol:Exit(1);
}
Var:Number status;
Curl:Status(status);
Konsol:Print("HTTP status: ${status}");
if (status != 200) {
Konsol:Print("Fetch failed - aborting.");
Konsol:Exit(1);
}
// ── Step 3: Split the HTML into lines for per-line regex matching ─────────────
// String:Split breaks the full HTML string on newlines, giving us a list of
// lines we can process one at a time - the same pattern used in log_analyzer.ks.
Var:List htmlLines;
String:Split(html, "\n", htmlLines);
Var:Number lineCount;
List:Size(htmlLines, lineCount);
Konsol:Print("Page size: ${lineCount} lines");
// ── Step 4: Extract the <title> ──────────────────────────────────────────────
// Scan each line; stop as soon as we find the title tag.
Var:List titleGroups;
Var:Number titleGc;
Var:String pageTitle = "(none)";
Var:Boolean titleFound = false;
for (Number li = 0; li < lineCount; li++) {
Var:String line;
List:Get(li, htmlLines, line);
if (!titleFound) {
List:Clear(titleGroups);
Regex:Groups("<title>([^<]+)</title>", line, titleGroups);
List:Size(titleGroups, titleGc);
if (titleGc >= 2) {
List:Get(1, titleGroups, pageTitle);
String:Trim(pageTitle, pageTitle);
titleFound = true;
}
}
}
Konsol:Print("Page title: ${pageTitle}");
// ── Step 5: Extract absolute href links ──────────────────────────────────────
// Regex:Groups finds the first match on each line.
// The pattern captures only http/https URLs to skip relative paths and anchors.
Var:List results;
Var:Number linkCount = 0;
Var:List hrefGroups;
Var:Number hrefGc;
for (Number li = 0; li < lineCount; li++) {
Var:String line;
List:Get(li, htmlLines, line);
List:Clear(hrefGroups);
Regex:Groups("href=\"(https?://[^\"]+)\"", line, hrefGroups);
List:Size(hrefGroups, hrefGc);
if (hrefGc >= 2) {
Var:String href;
List:Get(1, hrefGroups, href);
List:Push(href, results);
linkCount = linkCount + 1;
}
}
// Note: one href is captured per line. Minified HTML with many links on
// one line may be under-counted. Unminify first or extend the loop with
// a second Regex:Groups pass if needed.
Konsol:Print("External links found: ${linkCount}");
Konsol:Print("");
// ── Step 6: Print the results ────────────────────────────────────────────────
for (Number ri = 0; ri < linkCount; ri++) {
Var:String link;
List:Get(ri, results, link);
Var:Number num = ri + 1;
Konsol:Print(" [${num}] ${link}");
}
// ── Step 7: Optionally save to a text file ───────────────────────────────────
if (outFile != "") {
Var:Number wh;
File:Open(outFile, "w", wh);
File:Write("Scraped from: ${url}\n", wh);
File:Write("Title: ${pageTitle}\n", wh);
File:Write("Links:\n", wh);
for (Number si = 0; si < linkCount; si++) {
Var:String slink;
List:Get(si, results, slink);
File:Write("${slink}\n", wh);
}
File:Close(wh);
Konsol:Print("Saved to ${outFile}");
}
Konsol:Print("Done.");
Modules: net plugin · Konsol
Simple peer-to-peer text chat over a local network. One player hosts (h); others join (j) by entering the host's IP address. All messages are broadcast to every connected player.
Key patterns:
Net:Host / Net:Join - start or connect to a session on port 2310Net:Check every loop iteration - pumps the network stack for eventsNet:GetMessage loop - drain all pending messages before promptingUsage
# on the host machine
minks lan_chat.ks
# > Host or join? (h/j): h
# on each client machine (same LAN)
minks lan_chat.ks
# > Host or join? (h/j): j
# > Host IP address: 192.168.1.10
Sample session
=== LAN Chat ===
Host or join? (h/j): h
Your name: Alice
Starting session 'chatroom' on port 2310...
Waiting for players... (type a message to start chatting)
Type 'quit' to disconnect.
[Bob] hey Alice!
> Hello Bob!
[Bob] this is cool
> quit
Disconnected. Goodbye, Alice!
Script
// lan_chat.ks - peer-to-peer LAN messaging
// Modules: net plugin, Konsol
// Usage: minks lan_chat.ks
//
// Run once on the host machine (choose "h"), then run again on each
// client machine on the same LAN (choose "j"). All players share the
// session name "chatroom"; the host listens on port 2310.
//
// Limitation: Konsol:Input is blocking, so incoming messages only appear
// after you press Enter. For a fully asynchronous UI you would split
// sending and receiving into a producer/consumer pattern using two
// separate processes.
#include "net"
Konsol:Print("=== LAN Chat ===");
// ── Step 1: Choose role ───────────────────────────────────────────────────────
Var:String role;
Konsol:Input("Host or join? (h/j): ", role);
Var:String playerName;
Konsol:Input("Your name: ", playerName);
Var:Number handle;
if (role == "h") {
// Net:Host starts a session and waits for connections on port 2310.
// maxPlayers = 8 allows up to 8 participants (including the host).
Konsol:Print("Starting session 'chatroom' on port 2310...");
Net:Host(playerName, 8, "chatroom", handle);
Konsol:Print("Waiting for players... (type a message to start chatting)");
} else {
// Net:Join connects to a host by IP address.
Var:String hostIP;
Konsol:Input("Host IP address: ", hostIP);
Konsol:Print("Connecting to ${hostIP}...");
Net:Join(playerName, "chatroom", hostIP, handle);
Konsol:Print("Connected! (type a message to start chatting)");
}
Konsol:Print("Type 'quit' to disconnect.");
Konsol:Print("");
// ── Step 2: Chat loop ─────────────────────────────────────────────────────────
Var:Boolean running = true;
Var:String sender;
Var:String inMsg;
Var:Boolean got;
Var:String outMsg;
Var:Boolean sendOk;
while (running) {
// Net:Check must be called every iteration to pump the network stack
// and process connection/disconnection events.
Net:Check(handle);
// Drain all pending incoming messages before prompting for input.
// Net:GetMessage returns one message at a time; loop until got = false.
Net:GetMessage(handle, sender, inMsg, got);
while (got) {
Konsol:Print("[${sender}] ${inMsg}");
Net:GetMessage(handle, sender, inMsg, got);
}
// Block for user input, then send to all connected players.
Konsol:Input("> ", outMsg);
if (outMsg == "quit") {
running = false;
} else {
// Net:Send broadcasts to all connected players.
// Use Net:SendTo(handle, targetName, msg, ok) for direct messages.
Net:Send(handle, outMsg, sendOk);
if (!sendOk) {
Konsol:Print("Send failed - connection may be lost.");
running = false;
}
}
}
// ── Step 3: Clean up ─────────────────────────────────────────────────────────
Net:Quit(handle);
Konsol:Print("Disconnected. Goodbye, ${playerName}!");