1 | !7 |
2 | |
3 | // See #1023660 for the older 99 lines version |
4 | |
5 | concept PhysicalSlice { new Ref<Page> slicePage; } |
6 | |
7 | concept Page { |
8 | new Ref<PhysicalSlice> slice; |
9 | S globalID = aGlobalID(); |
10 | S url; |
11 | } |
12 | |
13 | concept Entry { |
14 | S globalID = aGlobalID(); |
15 | new Ref<Page> page; |
16 | int count; |
17 | S key, value; |
18 | S ip; |
19 | new Ref<Signer> signer; |
20 | } |
21 | |
22 | concept Signer { |
23 | S globalID = aGlobalID(); |
24 | S publicKey; |
25 | bool trusted; |
26 | S approvedBy; |
27 | } |
28 | |
29 | concept Session { |
30 | S cookie; |
31 | Page slicePage; |
32 | } |
33 | |
34 | static int displayLength = 140; |
35 | |
36 | static int lines; |
37 | sbool allowMultipleCasesInValues = true; |
38 | static ConceptFieldIndexDesc idx_latestEntries, idx_latestCreatedPages, idx_latestChangedPages; |
39 | |
40 | p { |
41 | dbIndexingCI(Page, 'url, Entry, 'key, Entry, 'value); |
42 | dbIndexing(Signer, 'publicKey); |
43 | dbIndexing(Session, 'cookie); |
44 | idx_latestCreatedPages = new ConceptFieldIndexDesc(Page, 'created); |
45 | idx_latestChangedPages = new ConceptFieldIndexDesc(Page, '_modified); |
46 | idx_latestEntries = new ConceptFieldIndexDesc(Entry, 'created); |
47 | |
48 | // Approve this machine's key |
49 | PKIKeyPair machineKey = agiBot_trustedKeyForMachine(); |
50 | if (machineKey != null) { |
51 | print("Approving this machine's key: " + machineKey.publicKey); |
52 | cset(uniq_sync(Signer, publicKey := machineKey.publicKey), trusted := true, approvedBy := "local"); |
53 | } |
54 | |
55 | lines = countLines(mySource()); |
56 | } |
57 | |
58 | // DB functions |
59 | |
60 | sbool hasPage(S url) { ret hasConceptWhereIC(Page, +url); } |
61 | sS getValue(Page page, S key) { |
62 | ret getString value(highestByField count(objectsWhereIC(findBackRefs(page, Entry), +key))); |
63 | } |
64 | sS pageDisplayName(Page page) { |
65 | S name = getValue(page, "read as"); |
66 | bool unnaturalName = nempty(name) && !eq(makeAGIDomain(name), page.url); |
67 | ret unnaturalName ? name + " " + squareBracketed(page.url) : or2(name, unpackAGIDomainOpt(page.url)); |
68 | } |
69 | |
70 | // Serve page |
71 | |
72 | set flag NoNanoHTTPD. html { |
73 | ret new Request().serve(uri, params); |
74 | } |
75 | |
76 | sclass Request { |
77 | S cookie; |
78 | Session session; |
79 | |
80 | O serve(S uri, SS params) { |
81 | new Matches m; |
82 | |
83 | cookie = cookieFromUser(); |
84 | if (cookie != null) |
85 | session = uniq_sync(Session, +cookie); |
86 | else |
87 | session = unlistedWithValues(Session); |
88 | |
89 | if (swic(uri, "/bot/", m)) ret serveBot("/" + m.rest(), params); |
90 | |
91 | S q = params.get('q); |
92 | S domain = or2(params.get('domain), domain()); |
93 | if (nempty(q)) { |
94 | domain = makeAGIDomain(q); |
95 | if (l(domain) > maximumDomainPartLength()) // escape with "domain=" |
96 | ret hrefresh("http://agi.blue/" + hquery(+domain, key := "read as", value := q)); |
97 | ret hrefresh("http://" + domain + (eq(q, domain) ? "" : "/" + hquery(key := "read as", value := q))); |
98 | //uri = "/"; replaceMapWithParams(params, key := "read as", value := q); |
99 | } |
100 | S url = domain + dropTrailingSlash(uri); |
101 | Page page, bool newPage = unpair uniqCI2_sync(Page, +url); |
102 | if (newPage) dbLog("New page", +url); |
103 | |
104 | 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"))) |
105 | + p(small(b(ahref("http://agi.blue", "agi.blue")) |
106 | + " | " + 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"))); |
107 | |
108 | if (eqicOneOf(url, "agi.blue", "www.agi.blue")) |
109 | ret hhtml(hhead_title("agi.blue Overview") // HOME PAGE |
110 | + hbody_fullcenter(top |
111 | + hform(b("GIVE ME INPUT:") + "<br><br>" + htextinput('q) + " " + hsubmit("Ask")) |
112 | + h1("agi.blue has " + nPages(countConcepts(Page))) |
113 | + p(nlToBr(nemptyLines(map(pageToHTMLLink(), sortedByFieldDesc _modified(list(Page)))))) |
114 | )); |
115 | |
116 | S key = trim(params.get('key)), value = trim(params.get('value)); |
117 | L<Entry> entries = GetEntriesAndPost().go(page, params).entries; |
118 | |
119 | S get = params.get('get); |
120 | if (nempty(get)) |
121 | ret serveText(jsonEncode(collect value(llNotNulls(firstWhereIC(entries, key := get))))); |
122 | |
123 | S key2 = key, value2 = value; if (nempty(key) && nempty(value)) key2 = value2 = ""; // input reset |
124 | |
125 | bool withHidden = eq(params.get('withHidden), "1"); |
126 | new Set<Int> hide; |
127 | if (!withHidden) for (Entry e : entries) if (eqic(e.key, 'hide) && isSquareBracketedInt(e.value)) addAll(hide, e.count, parseInt(unSquareBracket(e.value))); |
128 | new MultiMap<Int, S> mmMeta; |
129 | for (Entry e : entries) if (isSquareBracketedInt(e.key)) mmMeta.put(parseInt(unSquareBracket(e.key)), e.value); |
130 | new MultiMap<S> mm; |
131 | for (Entry e : entries) mm.put(e.key, e.value); |
132 | |
133 | S name = or2(/* ouch */ last(mm.get("read as")), /* end ouch */ unpackAGIDomain(page.url), page.url); |
134 | |
135 | L<Entry> valueRefs = conceptsWhereIC Entry(value := name); |
136 | Set<Page> valueRefPages = asSet(ccollect page(valueRefs)); |
137 | valueRefPages.remove(page); |
138 | |
139 | S mainContents = |
140 | top + h1(ahref_unstyled("http://" + url, htmlEncode2(name)) + (newPage ? " [huh????]" : "")) |
141 | + p(nlToBr(nemptyLines(map(entries, func(Entry e) -> S { |
142 | !withHidden && (hide.contains(e.count) || eqic(e.key, "read as") && eqic(e.value, name)) ? "" : "[" + e.count + "] " + |
143 | htmlencode2(e.key) + ": " + b( |
144 | isURL(e.value) |
145 | || cic(mmMeta.get(e.count), "is a URL") |
146 | || isAGIDomain(e.value) |
147 | ? ahref(fixAGILink(absoluteURL(e.value)), htmlencode2(shorten(displayLength, e.value))) |
148 | : ahref(agiBlue_linkForPhrase(e.value), htmlencode2(shorten(displayLength, e.value))) |
149 | ) })))) |
150 | + hpostform(h3("Add an entry") |
151 | + "Key: " + hinputfield(key := key2) + " Value: " + hinputfield(value := value2) + "<br><br>" + hsubmit("Add") |
152 | ); |
153 | |
154 | S sideContents = |
155 | hform(b("GIVE ME INPUT:") + " " + htextinput('q) + " " + hsubmit("Ask")) |
156 | |
157 | + h3("References (" + l(valueRefPages) + ")") |
158 | |
159 | + p(nlToBr(nemptyLines(map(pageToHTMLLink(), takeFirst(10, valueRefPages))))); |
160 | |
161 | // serve a page |
162 | ret hhtml(hhead_title(pageDisplayName(page)) + hbody( |
163 | tag('table, tr( |
164 | td(mainContents, align := 'center, valign := 'top) + |
165 | td(sideContents, align := 'right, valign := 'top)), |
166 | width := "100%", height := "100%"))); |
167 | } |
168 | } |
169 | |
170 | svoid dbLog(O... params) { |
171 | logStructure(programFile("db.log"), ll(params)); |
172 | } |
173 | |
174 | // uri = without the "/bot" in front |
175 | sO serveBot(S uri, SS params) { |
176 | S q = params.get('q), url = params.get('url); |
177 | if (nempty(q)) url = makeAGIDomain(q); |
178 | if (eqic(uri, "/hello")) ret serveJSON("hello"); |
179 | if (eqic(uri, "/hasPage")) ret serveJSON(hasPage(url)); |
180 | if (eqic(uri, "/randomPageContaining")) { |
181 | assertNempty(q); |
182 | ret servePageToBot(random(filter(list(Page), p -> cic(p.url, q))), params); |
183 | } |
184 | if (eqic(uri, "/allPagesEndingWith")) { |
185 | assertNempty(q); |
186 | ret servePagesToBot(filter(list(Page), p -> ewic(p.url, q)), params); |
187 | } |
188 | |
189 | if (eqicOneOf(uri, "/postSigned", "/makePhysicalSlice", "/approveTrustRequest")) { |
190 | S text = rtrim(params.get('text)); |
191 | S key = getSignerKey(text); |
192 | if (empty(key)) ret subBot_serve500("Please include your public key"); |
193 | if (!isSignedWithKey(text, key)) ret subBot_serve500("Signature didn't verify"); |
194 | text = dropLastTwoLines(text); // drop signer + sig line |
195 | |
196 | Signer signer = uniq_sync Signer(publicKey := key); |
197 | |
198 | if (eqic(uri, "/makePhysicalSlice")) { |
199 | if (!signer.trusted) ret subBot_serve500("Untrusted signer"); |
200 | Page page = findPageFromParams(jsonDecodeMap(text)); |
201 | if (page == null) ret subBot_serve500("Page not found"); |
202 | ret jsonEncode(uniq2_sync(PhysicalSlice, slicePage := page).b ? "Slice made" : "Slice exists"); |
203 | } |
204 | |
205 | if (eqic(uri, "/postSigned")) { |
206 | new L out; |
207 | for (S line : tlft(text)) { |
208 | SS map = jsonDecodeMap(line); |
209 | new GetEntriesAndPost x; |
210 | x.signer = signer; |
211 | Page page = findOrMakePageFromParams(map); |
212 | if (page == null) continue with out.add("Page not found: " + quote(url)); |
213 | x.go(page, map); |
214 | out.add(x.newEntry ? "Saved" : x.entry != null ? "Entry exists" : "Need key and value"); |
215 | } |
216 | ret serveJSON(out); |
217 | } |
218 | |
219 | if (eqic(uri, "/approveTrustRequest")) { |
220 | if (!signer.trusted) ret subBot_serve500("Untrusted signer"); |
221 | Signer toApprove = conceptWhere Signer(publicKey := trim(text)); |
222 | if (toApprove == null) ret subBot_serve500("Signer to approve not found"); |
223 | cset(toApprove, trusted := true, approvedBy := signer.globalID); |
224 | ret serveJSON("Approved: " + trim(text)); |
225 | } |
226 | |
227 | ret subBot_serve500("CONFUSION"); |
228 | } |
229 | |
230 | if (eqic(uri, "/post")) { |
231 | new GetEntriesAndPost x; |
232 | x.go(conceptWhereCI Page(+url), params); |
233 | ret serveJSON(x.newEntry ? "Saved" : x.entry != null ? "Entry exists" : "Need key and value"); |
234 | } |
235 | if (eqic(uri, "/latestEntries")) |
236 | ret serveJSON_shallowLineBreaks(map(takeFirst(10, idx_latestEntries.objectIterator()), entryToMap(true))); |
237 | if (eqic(uri, "/latestPages")) |
238 | ret serveJSON_shallowLineBreaks(map(takeFirst(10, idx_latestCreatedPages.objectIterator()), pageToMap())); |
239 | if (eqic(uri, "/latestChangedPages")) |
240 | ret serveJSON_shallowLineBreaks(map(takeFirst(10, idx_latestChangedPages.objectIterator()), pageToMap())); |
241 | |
242 | if (eqic(uri, "/totalPageCount")) |
243 | ret serveJSON(countConcepts(Page)); |
244 | if (eqic(uri, "/physicalSliceCount")) |
245 | ret serveJSON(countConcepts(PhysicalSlice)); |
246 | if (eqic(uri, "/trustedSignersCount")) |
247 | ret serveJSON(countConcepts(Signer, trusted := true)); |
248 | |
249 | if (eqic(uri, "/valueSearch")) { |
250 | S value = params.get('value); |
251 | L<Entry> entries = conceptsWhereIC Entry(+value); |
252 | ret serveJSON_shallowLineBreaks(map(takeFirst(100, entries), entryToMap(true)); |
253 | } |
254 | |
255 | // end of bot functions |
256 | |
257 | ret subBot_serve404(); |
258 | } |
259 | |
260 | static IF1<Page, Map> pageToMap(O... _) { |
261 | bool withEntries = boolPar withEntries(_); |
262 | ret (IF1<Page, Map>) p -> { |
263 | L<Entry> entries = findBackRefs(p, Entry); |
264 | ret litorderedmap( |
265 | url := p.url, |
266 | nEntries := l(entries), |
267 | created := p.created, |
268 | modified := p._modified, |
269 | entries := !withEntries ? null : map(entries, entryToMap(false))); |
270 | }; |
271 | } |
272 | |
273 | static IF1<Entry, Map> entryToMap(bool withPage) { |
274 | ret (IF1<Entry, Map>) e -> litorderedmap( |
275 | created := e.created, |
276 | i := e.count, |
277 | key := e.key, |
278 | value := e.value, |
279 | url := withPage ? e.page->url : null, |
280 | signer := getString globalID(e.signer!)); |
281 | } |
282 | |
283 | sO servePageToBot(Page page, SS params) { |
284 | if (page == null) ret serveJSON(null); |
285 | params = asCIMap(params); |
286 | Map map = pageToMap(withEntries := valueIs1 withEntries(params)).get(page); |
287 | ret serveJSON(map); |
288 | } |
289 | |
290 | sO servePagesToBot(Iterable<Page> pages, SS params) { |
291 | ret serveJSON(map(pageToMap(), pages)); |
292 | } |
293 | |
294 | sclass GetEntriesAndPost { |
295 | L<Entry> entries; |
296 | Entry entry; |
297 | bool newEntry; |
298 | Signer signer; |
299 | |
300 | GetEntriesAndPost go(Page page, SS params) { |
301 | S key = trim(params.get('key)), value = trim(params.get('value)); |
302 | withDBLock { |
303 | entries = findBackRefs(page, Entry); |
304 | if (nempty(key) && nempty(value)) { |
305 | S ip = subBot_clientIP(); |
306 | entry = firstThat(e -> eqic(e.key, key) && eq_icIf(!allowMultipleCasesInValues, e.value, value) && eq(e.ip, ip), entries); |
307 | if (entry == null) { |
308 | print("SAVING"); |
309 | Entry e = cnew Entry(+page, +key, +value, +ip, count := l(entries) + 1, +signer); |
310 | page.change(); // bump modification date |
311 | entry = e; |
312 | newEntry = true; |
313 | entries.add(entry); |
314 | dbLog("New entry", page := page.url, globalID := e.globalID, count := e.count, +key, +value); |
315 | } |
316 | } |
317 | } |
318 | |
319 | sortByFieldInPlace created(entries); |
320 | numberEntriesInConceptField count(entries); |
321 | this; |
322 | } |
323 | } |
324 | |
325 | static Page findPageFromParams(Map map) { |
326 | S url = nempty(getString q(map)) ? makeAGIDomain(getString q(map)) : getString url(map); |
327 | ret empty(url) ? null : conceptWhereCI Page(+url); |
328 | } |
329 | |
330 | static Page findOrMakePageFromParams(Map map) { |
331 | S url = nempty(getString q(map)) ? makeAGIDomain(getString q(map)) : getString url(map); |
332 | ret empty(url) ? null : uniqCI_sync Page(+url); |
333 | } |
334 | |
335 | static F1<Page, S> pageToHTMLLink() { |
336 | ret func(Page p) -> S { |
337 | ahref(fixAGILink("http://" + p.url), htmlEncode2(pageDisplayName(p))) |
338 | }; |
339 | } |
Began life as a copy of #1023558
download show line numbers debug dex old transpilations
Travelled to 6 computer(s): bhatertpkbcr, mqqgnosmbjvj, pyentgdyhuwx, pzhvpgtvlbxg, tvejysmllsmz, vouqrxazstgt
No comments. add comment
Snippet ID: | #1023708 |
Snippet name: | agi.blue source [backup before switching from url to q] |
Eternal ID of this version: | #1023708/1 |
Text MD5: | 34195892a56a499c7c979d24a90ad67e |
Author: | stefan |
Category: | javax / html |
Type: | JavaX module (desktop) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2019-07-03 22:29:11 |
Source code size: | 12857 bytes / 339 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 331 / 518 |
Referenced in: | [show references] |