!7 // See #1023660 for the older 99 lines version concept PhysicalSlice { new Ref slicePage; } concept Page { new Ref slice; S globalID = aGlobalID(); S url; } concept Entry { S globalID = aGlobalID(); new Ref page; int count; S key, value; S ip; new Ref signer; } concept Signer { S globalID = aGlobalID(); S publicKey; bool trusted; S approvedBy; } concept Session { S cookie; Page slicePage; } static int displayLength = 140; static int lines; sbool allowMultipleCasesInValues = true; static ConceptFieldIndexDesc idx_latestEntries, idx_latestCreatedPages, idx_latestChangedPages; p { dbIndexingCI(Page, 'url, Entry, 'key, Entry, 'value); dbIndexing(Signer, 'publicKey); dbIndexing(Session, 'cookie); idx_latestCreatedPages = new ConceptFieldIndexDesc(Page, 'created); idx_latestChangedPages = new ConceptFieldIndexDesc(Page, '_modified); idx_latestEntries = new ConceptFieldIndexDesc(Entry, 'created); // Approve this machine's key PKIKeyPair machineKey = agiBot_trustedKeyForMachine(); if (machineKey != null) { print("Approving this machine's key: " + machineKey.publicKey); cset(uniq_sync(Signer, publicKey := machineKey.publicKey), trusted := true, approvedBy := "local"); } lines = countLines(mySource()); } // DB functions sbool hasPage(S url) { ret hasConceptWhereIC(Page, +url); } sS getValue(Page page, S key) { ret getString value(highestByField count(objectsWhereIC(findBackRefs(page, Entry), +key))); } sS pageDisplayName(Page page) { S name = getValue(page, "read as"); bool unnaturalName = nempty(name) && !eq(makeAGIDomain(name), page.url); ret unnaturalName ? name + " " + squareBracketed(page.url) : or2(name, unpackAGIDomainOpt(page.url)); } // Serve page set flag NoNanoHTTPD. html { ret new Request().serve(uri, params); } sclass Request { S cookie; Session session; O serve(S uri, SS params) { new Matches m; cookie = cookieFromUser(); if (cookie != null) session = uniq_sync(Session, +cookie); else session = unlistedWithValues(Session); if (swic(uri, "/bot/", m)) ret serveBot("/" + m.rest(), params); S q = params.get('q); S domain = or2(params.get('domain), domain()); if (nempty(q)) { domain = makeAGIDomain(q); if (l(domain) > maximumDomainPartLength()) // escape with "domain=" ret hrefresh("http://agi.blue/" + hquery(+domain, key := "read as", value := q)); ret hrefresh("http://" + domain + (eq(q, domain) ? "" : "/" + hquery(key := "read as", value := q))); //uri = "/"; replaceMapWithParams(params, key := "read as", value := q); } S url = domain + dropTrailingSlash(uri); Page page, bool newPage = unpair uniqCI2_sync(Page, +url); if (newPage) dbLog("New page", +url); S top = hcomment("cookie: " + takeFirst(4, session.cookie)) + p(ahref("http://agi.blue", hsnippetimg(#1101682, width := 565/5, height := 800/5, title := "Robot by Peerro @ DeviantArt"))) + p(small(b(ahref("http://agi.blue", "agi.blue")) + " | " + targetBlank(progLink(), "source code") + " of this web site (" + nLines(lines) + ") | " + targetBlank("https://gitter.im/agi-blue/community", "sponsor https?") + " | by " + targetBlank("https://BotCompany.de", "BC") + " | " + targetBlank("http://fiverr.tinybrain.de/", "Fiverr") + " | " + targetBlank("https://discordapp.com/invite/SEAjPqk", "Discord") + " | " + targetBlank("https://www.youtube.com/watch?v=b6jtRdV3Ev8", "Video"))); if (eqicOneOf(url, "agi.blue", "www.agi.blue")) ret hhtml(hhead_title("agi.blue Overview") // HOME PAGE + hbody_fullcenter(top + hform(b("GIVE ME INPUT:") + "

" + htextinput('q) + " " + hsubmit("Ask")) + h1("agi.blue has " + nPages(countConcepts(Page))) + p(nlToBr(nemptyLines(map(pageToHTMLLink(), sortedByFieldDesc _modified(list(Page)))))) )); S key = trim(params.get('key)), value = trim(params.get('value)); L entries = GetEntriesAndPost().go(page, params).entries; S get = params.get('get); if (nempty(get)) ret serveText(jsonEncode(collect value(llNotNulls(firstWhereIC(entries, key := get))))); S key2 = key, value2 = value; if (nempty(key) && nempty(value)) key2 = value2 = ""; // input reset bool withHidden = eq(params.get('withHidden), "1"); new Set hide; if (!withHidden) for (Entry e : entries) if (eqic(e.key, 'hide) && isSquareBracketedInt(e.value)) addAll(hide, e.count, parseInt(unSquareBracket(e.value))); new MultiMap mmMeta; for (Entry e : entries) if (isSquareBracketedInt(e.key)) mmMeta.put(parseInt(unSquareBracket(e.key)), e.value); new MultiMap mm; for (Entry e : entries) mm.put(e.key, e.value); S name = or2(/* ouch */ last(mm.get("read as")), /* end ouch */ unpackAGIDomain(page.url), page.url); L valueRefs = conceptsWhereIC Entry(value := name); Set valueRefPages = asSet(ccollect page(valueRefs)); valueRefPages.remove(page); S mainContents = top + h1(ahref_unstyled("http://" + url, htmlEncode2(name)) + (newPage ? " [huh????]" : "")) + p(nlToBr(nemptyLines(map(entries, func(Entry e) -> S { !withHidden && (hide.contains(e.count) || eqic(e.key, "read as") && eqic(e.value, name)) ? "" : "[" + e.count + "] " + htmlencode2(e.key) + ": " + b( isURL(e.value) || cic(mmMeta.get(e.count), "is a URL") || isAGIDomain(e.value) ? ahref(fixAGILink(absoluteURL(e.value)), htmlencode2(shorten(displayLength, e.value))) : ahref(agiBlue_linkForPhrase(e.value), htmlencode2(shorten(displayLength, e.value))) ) })))) + hpostform(h3("Add an entry") + "Key: " + hinputfield(key := key2) + " Value: " + hinputfield(value := value2) + "

" + hsubmit("Add") ); S sideContents = hform(b("GIVE ME INPUT:") + " " + htextinput('q) + " " + hsubmit("Ask")) + h3("References (" + l(valueRefPages) + ")") + p(nlToBr(nemptyLines(map(pageToHTMLLink(), takeFirst(10, valueRefPages))))); // serve a page ret hhtml(hhead_title(pageDisplayName(page)) + hbody( tag('table, tr( td(mainContents, align := 'center, valign := 'top) + td(sideContents, align := 'right, valign := 'top)), width := "100%", height := "100%"))); } } svoid dbLog(O... params) { logStructure(programFile("db.log"), ll(params)); } // uri = without the "/bot" in front sO serveBot(S uri, SS params) { S q = params.get('q), url = params.get('url); if (nempty(q)) url = makeAGIDomain(q); if (eqic(uri, "/hello")) ret serveJSON("hello"); if (eqic(uri, "/hasPage")) ret serveJSON(hasPage(url)); if (eqic(uri, "/randomPageContaining")) { assertNempty(q); ret servePageToBot(random(filter(list(Page), p -> cic(p.url, q))), params); } if (eqic(uri, "/allPagesEndingWith")) { assertNempty(q); ret servePagesToBot(filter(list(Page), p -> ewic(p.url, q)), params); } if (eqicOneOf(uri, "/postSigned", "/makePhysicalSlice", "/approveTrustRequest")) { S text = rtrim(params.get('text)); S key = getSignerKey(text); if (empty(key)) ret subBot_serve500("Please include your public key"); if (!isSignedWithKey(text, key)) ret subBot_serve500("Signature didn't verify"); text = dropLastTwoLines(text); // drop signer + sig line Signer signer = uniq_sync Signer(publicKey := key); if (eqic(uri, "/makePhysicalSlice")) { if (!signer.trusted) ret subBot_serve500("Untrusted signer"); Page page = findPageFromParams(jsonDecodeMap(text)); if (page == null) ret subBot_serve500("Page not found"); ret jsonEncode(uniq2_sync(PhysicalSlice, slicePage := page).b ? "Slice made" : "Slice exists"); } if (eqic(uri, "/postSigned")) { new L out; for (S line : tlft(text)) { SS map = jsonDecodeMap(line); new GetEntriesAndPost x; x.signer = signer; Page page = findOrMakePageFromParams(map); if (page == null) continue with out.add("Page not found: " + quote(url)); x.go(page, map); out.add(x.newEntry ? "Saved" : x.entry != null ? "Entry exists" : "Need key and value"); } ret serveJSON(out); } if (eqic(uri, "/approveTrustRequest")) { if (!signer.trusted) ret subBot_serve500("Untrusted signer"); Signer toApprove = conceptWhere Signer(publicKey := trim(text)); if (toApprove == null) ret subBot_serve500("Signer to approve not found"); cset(toApprove, trusted := true, approvedBy := signer.globalID); ret serveJSON("Approved: " + trim(text)); } ret subBot_serve500("CONFUSION"); } if (eqic(uri, "/post")) { new GetEntriesAndPost x; x.go(conceptWhereCI Page(+url), params); ret serveJSON(x.newEntry ? "Saved" : x.entry != null ? "Entry exists" : "Need key and value"); } if (eqic(uri, "/latestEntries")) ret serveJSON_shallowLineBreaks(map(takeFirst(10, idx_latestEntries.objectIterator()), entryToMap(true))); if (eqic(uri, "/latestPages")) ret serveJSON_shallowLineBreaks(map(takeFirst(10, idx_latestCreatedPages.objectIterator()), pageToMap())); if (eqic(uri, "/latestChangedPages")) ret serveJSON_shallowLineBreaks(map(takeFirst(10, idx_latestChangedPages.objectIterator()), pageToMap())); if (eqic(uri, "/totalPageCount")) ret serveJSON(countConcepts(Page)); if (eqic(uri, "/physicalSliceCount")) ret serveJSON(countConcepts(PhysicalSlice)); if (eqic(uri, "/trustedSignersCount")) ret serveJSON(countConcepts(Signer, trusted := true)); if (eqic(uri, "/valueSearch")) { S value = params.get('value); L entries = conceptsWhereIC Entry(+value); ret serveJSON_shallowLineBreaks(map(takeFirst(100, entries), entryToMap(true)); } // end of bot functions ret subBot_serve404(); } static IF1 pageToMap(O... _) { bool withEntries = boolPar withEntries(_); ret (IF1) p -> { L entries = findBackRefs(p, Entry); ret litorderedmap( url := p.url, nEntries := l(entries), created := p.created, modified := p._modified, entries := !withEntries ? null : map(entries, entryToMap(false))); }; } static IF1 entryToMap(bool withPage) { ret (IF1) e -> litorderedmap( created := e.created, i := e.count, key := e.key, value := e.value, url := withPage ? e.page->url : null, signer := getString globalID(e.signer!)); } sO servePageToBot(Page page, SS params) { if (page == null) ret serveJSON(null); params = asCIMap(params); Map map = pageToMap(withEntries := valueIs1 withEntries(params)).get(page); ret serveJSON(map); } sO servePagesToBot(Iterable pages, SS params) { ret serveJSON(map(pageToMap(), pages)); } sclass GetEntriesAndPost { L entries; Entry entry; bool newEntry; Signer signer; GetEntriesAndPost go(Page page, SS params) { S key = trim(params.get('key)), value = trim(params.get('value)); withDBLock { entries = findBackRefs(page, Entry); if (nempty(key) && nempty(value)) { S ip = subBot_clientIP(); entry = firstThat(e -> eqic(e.key, key) && eq_icIf(!allowMultipleCasesInValues, e.value, value) && eq(e.ip, ip), entries); if (entry == null) { print("SAVING"); Entry e = cnew Entry(+page, +key, +value, +ip, count := l(entries) + 1, +signer); page.change(); // bump modification date entry = e; newEntry = true; entries.add(entry); dbLog("New entry", page := page.url, globalID := e.globalID, count := e.count, +key, +value); } } } sortByFieldInPlace created(entries); numberEntriesInConceptField count(entries); this; } } static Page findPageFromParams(Map map) { S url = nempty(getString q(map)) ? makeAGIDomain(getString q(map)) : getString url(map); ret empty(url) ? null : conceptWhereCI Page(+url); } static Page findOrMakePageFromParams(Map map) { S url = nempty(getString q(map)) ? makeAGIDomain(getString q(map)) : getString url(map); ret empty(url) ? null : uniqCI_sync Page(+url); } static F1 pageToHTMLLink() { ret func(Page p) -> S { ahref(fixAGILink("http://" + p.url), htmlEncode2(pageDisplayName(p))) }; }