From 3f69a8a94db5c4f83f138a5188522dbac3737c47 Mon Sep 17 00:00:00 2001 From: octospacc Date: Tue, 25 Apr 2023 00:55:20 +0200 Subject: [PATCH] Update --- App/ApiTransform.js | 10 +++++++++ App/Elems.js | 24 +++++++++++++++------ App/Friendiiverse.html | 4 +++- App/Lib/cleanHTML.js | 47 ++++++++++++++++++++++++++++++++++++++++++ App/Main.js | 44 +++++++++++++++++++++++++++++++-------- App/Net.js | 2 +- App/Polyfill.js | 9 ++++++++ App/Style.css | 3 +++ App/Utils.js | 7 +++++-- 9 files changed, 131 insertions(+), 19 deletions(-) create mode 100644 App/Lib/cleanHTML.js create mode 100644 App/Polyfill.js diff --git a/App/ApiTransform.js b/App/ApiTransform.js index 444ab56..8be498a 100644 --- a/App/ApiTransform.js +++ b/App/ApiTransform.js @@ -38,6 +38,7 @@ var ApiSchema = { }, Quoting: { Mastodon: "reblog", + Misskey: "renote", }, Time: { Mastodon: "created_at", @@ -78,7 +79,16 @@ var ApiSchema = { }, }; +var ApiEndpoints = { + FetchNotes: { + Mastodon(Profile) { + return `accounts/${Profile.Id}/statuses`; + }, + }, +}; + function ApiTransform(Data, FromSource, DestType) { + LogDebug([Data, DestType, FromSource]); return JsonTransformB(Data, ApiSchema, ApiSchema[DestType], FromSource); }; diff --git a/App/Elems.js b/App/Elems.js index d7d62d3..2775778 100644 --- a/App/Elems.js +++ b/App/Elems.js @@ -1,4 +1,4 @@ -function HtmlEl(Tag, Attrs) { +function MkHtmlEl(Tag, Attrs) { var El = document.createElement(Tag); if (Attrs) { Object.keys(Attrs).forEach(function(Attr){ @@ -8,15 +8,27 @@ function HtmlEl(Tag, Attrs) { return El; }; -function MakeWindow(Attrs) { - var Window = HtmlEl('div', Attrs); +function MkWindow(Attrs) { + var Window = MkHtmlEl('div', Attrs); Window.className += ' Window'; Root.appendChild(Window); return Window; }; -function Dropdown(Attrs) { - var Menu = HtmlEl('div', Attrs); - Window.className += ' Dropdown'; +function MkSelectMenu(Opts, Attrs) { + var Menu = MkHtmlEl('div', Attrs); + var OptsHtml = ''; + Opts.forEach(function(Opt){ + OptsHtml += '
  • ' + MkHtmlEl('button', Opt).outerHTML + '
  • '; + }); + Menu.innerHTML = ` + + + `; + Menu.className += ' SelectMenu'; return Menu; }; diff --git a/App/Friendiiverse.html b/App/Friendiiverse.html index aa79bb3..a068e5f 100644 --- a/App/Friendiiverse.html +++ b/App/Friendiiverse.html @@ -12,7 +12,7 @@ var FriendicaCredentials = 'redacted:redacted'; // Development var Debug = true; -var UseFakeApi = true; +var UseFakeApi = false; + + diff --git a/App/Lib/cleanHTML.js b/App/Lib/cleanHTML.js new file mode 100644 index 0000000..affa794 --- /dev/null +++ b/App/Lib/cleanHTML.js @@ -0,0 +1,47 @@ +/*! + * Sanitize an HTML string + * (c) 2021 Chris Ferdinandi, MIT License, https://gomakethings.com + * https://vanillajstoolkit.com/helpers/cleanhtml/ + * (Modified from original) + */ +function SanitizeHtml(str, nodes) { + function stringToHTML() { + var parser = new DOMParser(); + var doc = parser.parseFromString(str, 'text/html'); + return doc.body || document.createElement('body'); + }; + function removeScripts(html) { + Array.from(html.querySelectorAll('script')).forEach(function(script){ + script.remove(); + }); + }; + function isPossiblyDangerous(name, value) { + var val = value.replace(/\s+/g, '').toLowerCase(); + if (['src', 'href', 'xlink:href'].includes(name)) { + if (val.includes('javascript:') || val.includes('data:')) return true; + }; + if (name.startsWith('on')) return true; + }; + function removeAttributes(elem) { + // Loop through each attribute + // If it's dangerous, remove it + Array.from(elem.attributes).forEach(function(att){ + if (isPossiblyDangerous(att.name, att.value)) + elem.removeAttribute(att.name); + }); + }; + function clean(html) { + Array.from(html.children).forEach(function(node){ + removeAttributes(node); + clean(node); + }); + }; + // Convert the string to HTML + var html = stringToHTML(); + // Sanitize it + removeScripts(html); + clean(html); + // If the user wants HTML nodes back, return them + // Otherwise, pass a sanitized string back + return nodes ? html.childNodes : html.innerHTML; +}; diff --git a/App/Main.js b/App/Main.js index 017c575..fd29f6f 100644 --- a/App/Main.js +++ b/App/Main.js @@ -1,7 +1,16 @@ var Persist = {Servers: {}, Sources: {}, Identities: {},}; var Present = CopyObj(Persist); var Tasker = {}; -var ApiCache = {Urls: {},}; +var ApiCache = { + __Store__(Data, Key, Where) { + ApiCache[Where][Key] = Data; + ApiCache[Where][Key].__Time__ = Date.now(); + }, + __UrlStore__(Data) { + ApiCache.__Store__(Data, Data.Url, 'Urls'); + }, + Urls: {}, +}; Assets._ = function _(Name) { if (Name in Assets) { @@ -13,6 +22,17 @@ Assets._ = function _(Name) { }; }; +Strings._ = function _(Name) { + // TODO: Handle arbitrary nestation + if (Name in Strings) { + if (Strings[Name]['en']) { // TODO{ Select this language from user config + return Strings[Name]['en']; + } else { + return Strings[Name].en; + }; + }; +}; + function DoAsync(First, Then, Data) { var Job = RndId(); Tasker[Job] = { @@ -46,7 +66,7 @@ function DoAsync(First, Then, Data) { }; function DisplayProfile(Profile) { - var Window = MakeWindow({className: "Profile"}); + var Window = MkWindow({className: "Profile"}); Window.innerHTML += `
    @@ -62,14 +82,19 @@ function DisplayProfile(Profile) { }; function FetchNotes(Profile, Proc) { - + var Soft = Profile.__Software__; + NetApiCall({Target: Soft, Method: ApiEndpoints.FetchNotes[Soft](Profile), CallFine: function(Res){ + var Notes = ApiTransform(Res.responseJson, 'Mastodon', 'Note'); + LogDebug(Notes, 'l'); + Tasker[Res.Proc[0]].Return(Notes); + }}, Proc); }; function FetchMastodon(Proc) { if (UseFakeApi) { ResFetchMastodon({responseJson: [FakeApi.Mastodon.Status], Proc: Proc}); } else { - ApiCall({Target: "Mastodon", Method: "timelines/public", CallFine: ResFetchMastodon}, Proc); + NetApiCall({Target: "Mastodon", Method: "timelines/public", CallFine: ResFetchMastodon}, Proc); }; }; function ResFetchMastodon(Res) { @@ -80,12 +105,13 @@ function ResFetchMastodon(Res) { function FillTimeline(Notes) { Notes.forEach(function(Note){ + ApiCache.__UrlStore__(Note.Profile); Root.lastChild.innerHTML += `
    ${Note.Profile.Name} - ${Note.Content} + ${SanitizeHtml(Note.Content)} ${Note.Time}
    `; }); @@ -106,7 +132,7 @@ function FetchFeatured(Proc) { }; function FillFeatured(Categories) { - var Window = MakeWindow({className: "Gallery"}); + var Window = MkWindow({className: "Gallery"}); Object.values(Categories).forEach(function(Profiles){ Profiles.forEach(function(Profile){ Window.innerHTML += `
    @@ -141,7 +167,7 @@ PlazasView.innerHTML = ` */ function ComposeNote() { - var Window = MakeWindow(); + var Window = MkWindow(); Window.innerHTML += `

    Compose

    Posting in: [Channel]

    @@ -158,11 +184,11 @@ function PostNote(Text) { }; function ManageSettings() { - MakeWindow().innerHTML = ` + MkWindow().innerHTML = `

    Settings

    Misc

    - Language: ${Dropdown()} + Language: ${MkSelectMenu([{innerHTML: "en"}, {innerHTML: "it"},]).outerHTML}

    Theme: diff --git a/App/Net.js b/App/Net.js index c67b222..b4a7ce6 100644 --- a/App/Net.js +++ b/App/Net.js @@ -1,4 +1,4 @@ -function ApiCall(Data, Proc) { +function NetApiCall(Data, Proc) { // Data = {Target: "Friendica", Method: "...", Data: {}, Call: (), CallFine: (), CallFail: ()} var Req = new XMLHttpRequest(); Req.Proc = Proc; diff --git a/App/Polyfill.js b/App/Polyfill.js new file mode 100644 index 0000000..450b9fb --- /dev/null +++ b/App/Polyfill.js @@ -0,0 +1,9 @@ +if (!Array.prototype.from) { + Array.prototype.from = function from(List) { + var Arr = []; + for (var i=0; i