Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP implement mentions + findUser falls back to webfinger #28

Merged
merged 2 commits into from
Mar 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 39 additions & 19 deletions philip/src/components/Publish.svelte
Original file line number Diff line number Diff line change
@@ -1,55 +1,73 @@
<script>
import { getCreateObject, getHashTag } from "../utils/pubGate";
import { getCreateObject, getHashTag, getMention } from "../utils/pubGate";
import { getUserId } from "../utils";

export let reply = null;
export let session;
export let curRoute;

let inProgress = false;
let content = "";
let error = "";

const hashTagMatcher = /(^|\W)(#[^#\s]+)/gi;
const mentionMatcher = /(^|\W)@([^@\s]+)(@([^@\s]+))?/gi;

const wrapHashTagsWithLink = text =>
text.match(hashTagMatcher)
? text.replace(hashTagMatcher, '$1<a href="" rel="tag">$2</a>')
: text;
text.replace(hashTagMatcher, '$1<a href="" rel="tag">$2</a>');

const getAllHashTags = text => text.match(hashTagMatcher) || [];
const getAllMentions = text => [...text.matchAll(mentionMatcher)] || [];

const wrapLinksWithTags = text =>
text.replace(/(https?:\/\/([^\s]+))/gi, '<a href="$1">$2</a>');

const publish = async ev => {
const publish = ev => {
ev.preventDefault();

inProgress = true;
let tags = getAllHashTags(content)

const tags = getAllHashTags(content)
.map(v => v.trim())
.map(getHashTag);

const data = wrapHashTagsWithLink(wrapLinksWithTags(content));

let ap_object = getCreateObject(data, tags);
content = wrapHashTagsWithLink(wrapLinksWithTags(content));

// parse and replace mentions
const mentions = getAllMentions(content).map(m => {
const orig = m[0];
const name = m[2];
const domain = m[4];
const id = getUserId(name, domain);
const wrapped = `${m[1]}<span class='h-card'><a href="${id}"' class='u-url mention'>@<span>${name}</span></a></span>`;
content = content.replace(orig, wrapped);
return getMention(name, id);
});
let ap_object = getCreateObject(content, tags.concat(mentions));
ap_object.cc = mentions.map(m => m.href);

if (reply) {
ap_object.object.inReplyTo = reply.id;
ap_object.cc = [reply.attributedTo];
ap_object.cc = ap_object.cc.concat(reply.attributedTo);
}
sendPost(JSON.stringify(ap_object));
};

const sendPost = async body => {
try {
const response = await fetch($session.user.outbox, {
method: "POST",
body: JSON.stringify(ap_object),
headers: { Authorization: "Bearer " + $session.token },
});
const data = await response.json();
const headers = { Authorization: "Bearer " + $session.token };
const req = { method: "POST", body, headers };
console.log("sending", req);
const res = await fetch($session.user.outbox, req).then(d => d.json());
console.log("response", res);
if (res.error) error = res.error;
else if (res.Created !== "success")
error = "Failed to create post: " + JSON.stringify(res);
} catch (e) {
console.log(e);
error = e;
}

inProgress = false;
content = "";
// TODO change route to show post?
};
</script>

Expand All @@ -75,3 +93,5 @@
</button>

</form>

<p class="text-danger">{error}</p>
61 changes: 25 additions & 36 deletions philip/src/components/Search.svelte
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
<script>
export let session;
export let curRoute;
const protocol = base_url.match(/^https/) ? "https" : "http";

import xhr from "../utils/xhr";
import { xhr, findUser, fetchOutbox } from "../utils";
import TimeLine from "./TimeLine.svelte";
import Post from "./Post.svelte";
import { readable } from "svelte/store";
Expand All @@ -20,50 +19,40 @@
let postLink = "";
let error = "";

async function search(event) {
const search = async event => {
error = "";
profile = null;
outbox_collection = null;
let pair = username.split("@");
let name, domain, url;
if (username.match(/^http/)) {
url = username; // TODO
error = "we could do the request for you, but we don't";
return;
}

const pair = username.split("@");
if (pair.length !== 2) {
return (error = "Use this format: username@domain");
}
let profile_url = `${protocol}://${pair[1]}/@${pair[0]}`;
name = pair[0];
domain = pair[1];

if (pubgate_instance) {
console.log("search", profile_url);
const res = await fetch(base_url + "/proxy", {
method: "POST",
body: JSON.stringify({ url: profile_url }),
}).then(d => d.json());
if (res.error) {
return (error = JSON.stringify(res.error.strerror || res.error));
}
profile = res;
const res = await handleResult(findUser(name, domain));

const body = JSON.stringify({ url: profile.outbox });
const req = { method: "POST", body };
const resp = await fetch(base_url + "/proxy", req).then(d => d.json());
if (!resp) {
return (error = "Failed to fetch timeline.");
}
if (!res.outbox) return;
profile = res;

outbox_collection =
typeof resp.first === "string"
? await fetchTimeline(resp.first)
: resp.first;
} else {
const headers = { Accept: "application/activity+json" };
const response = await fetch(profile_url, headers).then(d => d.json());
if (profile.outbox) outbox_collection = profile.outbox;
}
}
outbox_collection =
typeof profile.outbox === "string"
? await handleResult(fetchOutbox(profile.outbox))
: profile.outbox;
};

const fetchTimeline = async url => {
return await fetch(base_url + "/proxy", {
method: "POST",
body: JSON.stringify({ url: res.first }),
}).then(d => d.json());
const handleResult = async promise => {
const result = await promise;
if (!result) error = "Empty response.";
else if (result.error) error = result.error;
return result;
};

const follow = async event => {
Expand Down
1 change: 1 addition & 0 deletions philip/src/utils/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { default as xhr } from "./xhr";
export { ensureObject } from "./objectUtils";
export { getUserId, findUser, fetchUser, fetchOutbox } from "./user";
8 changes: 3 additions & 5 deletions philip/src/utils/pubGate.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
export const getHashTag = name => ({
name,
href: "",
type: "Hashtag",
});
export const getHashTag = name => ({ name, href: "", type: "Hashtag" });

export const getMention = (name, href) => ({ name, href, type: "Mention" });

export const getCreateObject = (content, tag) => ({
type: "Create",
Expand Down
56 changes: 56 additions & 0 deletions philip/src/utils/user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
let baseProtocol, baseDomain;
const m = base_url.match(/^([^:]+):\/\/([^/]+)/);
if (m) {
baseProtocol = m[1];
baseDomain = m[2];
}

export const getUserId = (name, domain = baseDomain, fyn = true) => {
const protocol = domain === baseDomain ? baseProtocol : "https";
return (
`${protocol}://${domain}/` +
(fyn ? `@${name}` : `.well-known/webfinger?resource=acc:${name}@${domain}`)
);
};

export const findUser = async (name, domain) => {
const useProxy = pubgate_instance ? true : false;
const fynRes = await fetchUser(getUserId(name, domain), useProxy);
console.log("fyn", fynRes);
if (fynRes && !fynRes.error) return fynRes;

const wfRes = await fetchUser(getUserId(name, domain, false), useProxy);
console.log("wf", wfRes);
if (!wfRes || wfRes.error) return wfRes;
const id = wfRes.aliases[1] || wfRes.links[0].href;
return await fetchUser(id, useProxy);
};

export const fetchUser = async (url, useProxy = true) => {
if (useProxy) {
//TODO require auth, merge with xhr?
const req = { method: "POST", body: JSON.stringify({ url }) };
return await fetch(base_url + "/proxy", req).then(d => d.json());
}
try {
// might not return json
const headers = { Accept: "application/activity+json" };
return await fetch(url, headers).then(d => d.json());
} catch (error) {
return { error };
}
};

export const fetchOutbox = async url => {
const req = { method: "POST", body: JSON.stringify({ url }) };
const res = await fetch(base_url + "/proxy", req).then(d => d.json());
if (res.error) return res;
return typeof res.first === "string"
? await fetchTimeline(res.first)
: res.first;
};

const fetchTimeline = async url => {
const req = { method: "POST", body: JSON.stringify({ url }) };
return await fetch(base_url + "/proxy", request).then(d => d.json());
};