This commit is contained in:
Bryan Ashby 2023-01-08 13:26:52 -07:00
parent f86d9338a1
commit 2370185bcc
No known key found for this signature in database
GPG Key ID: C2C1B501E4EFD994
2 changed files with 319 additions and 321 deletions

View File

@ -29,13 +29,7 @@ module.exports = class Actor {
this.outbox = ''; this.outbox = '';
this.following = ''; this.following = '';
this.followers = ''; this.followers = '';
this.liked = '';
} }
this.actorId = 0;
this.actorUrl = '';
this.properties = {}; // name:value
this.groups = []; // group membership(s)
} }
isValid() { isValid() {
@ -60,7 +54,10 @@ module.exports = class Actor {
return true; return true;
} }
static getRemoteActor(url, cb) { // :TODO: create an Actor object from a local user
static fromLocalUser(userId, cb) {}
static fromRemoteUrl(url, cb) {
const headers = { const headers = {
Accept: 'application/activity+json', Accept: 'application/activity+json',
}; };
@ -106,360 +103,361 @@ module.exports = class Actor {
return new Actor(parsed); return new Actor(parsed);
} }
create(cb) { // :TODO: persist()?
assert(0 === this.actorId); // create(cb) {
// assert(0 === this.actorId);
if (_.isEmpty(this.actorUrl)) { // if (_.isEmpty(this.actorUrl)) {
return cb(Errors.Invalid('Blank actor url')); // return cb(Errors.Invalid('Blank actor url'));
} // }
const self = this; // const self = this;
async.waterfall( // async.waterfall(
[ // [
function beginTransaction(callback) { // function beginTransaction(callback) {
return actorDb.beginTransaction(callback); // return actorDb.beginTransaction(callback);
}, // },
function createActorRec(trans, callback) { // function createActorRec(trans, callback) {
trans.run( // trans.run(
`INSERT INTO actor (actor_url) // `INSERT INTO actor (actor_url)
VALUES (?);`, // VALUES (?);`,
[self.actorUrl], // [self.actorUrl],
function inserted(err) { // function inserted(err) {
// use classic function for |this| // // use classic function for |this|
if (err) { // if (err) {
return callback(err); // return callback(err);
} // }
self.actorId = this.lastID; // self.actorId = this.lastID;
return callback(null, trans); // return callback(null, trans);
} // }
); // );
}, // },
function saveAll(trans, callback) { // function saveAll(trans, callback) {
self.persistWithTransaction(trans, err => { // self.persistWithTransaction(trans, err => {
return callback(err, trans); // return callback(err, trans);
}); // });
}, // },
function sendEvent(trans, callback) { // function sendEvent(trans, callback) {
Events.emit(Events.getSystemEvents().NewActor, { // Events.emit(Events.getSystemEvents().NewActor, {
actor: Object.assign({}, self, {}), // actor: Object.assign({}, self, {}),
}); // });
return callback(null, trans); // return callback(null, trans);
}, // },
], // ],
(err, trans) => { // (err, trans) => {
if (trans) { // if (trans) {
trans[err ? 'rollback' : 'commit'](transErr => { // trans[err ? 'rollback' : 'commit'](transErr => {
return cb(err ? err : transErr); // return cb(err ? err : transErr);
}); // });
} else { // } else {
return cb(err); // return cb(err);
} // }
} // }
); // );
} // }
persistWithTransaction(trans, cb) { // persistWithTransaction(trans, cb) {
assert(this.actorId > 0); // assert(this.actorId > 0);
const self = this; // const self = this;
async.series( // async.series(
[ // [
function saveProps(callback) { // function saveProps(callback) {
self.persistProperties(self.properties, trans, err => { // self.persistProperties(self.properties, trans, err => {
return callback(err); // return callback(err);
}); // });
}, // },
], // ],
err => { // err => {
return cb(err); // return cb(err);
} // }
); // );
} // }
static persistPropertyByActorId(actorId, propName, propValue, cb) { // static persistPropertyByActorId(actorId, propName, propValue, cb) {
actorDb.run( // actorDb.run(
`REPLACE INTO activitypub_actor_property (actor_id, prop_name, prop_value) // `REPLACE INTO activitypub_actor_property (actor_id, prop_name, prop_value)
VALUES (?, ?, ?);`, // VALUES (?, ?, ?);`,
[actorId, propName, propValue], // [actorId, propName, propValue],
err => { // err => {
if (cb) { // if (cb) {
return cb(err, propValue); // return cb(err, propValue);
} // }
} // }
); // );
} // }
setProperty(propName, propValue) { // setProperty(propName, propValue) {
this.properties[propName] = propValue; // this.properties[propName] = propValue;
} // }
incrementProperty(propName, incrementBy) { // incrementProperty(propName, incrementBy) {
incrementBy = incrementBy || 1; // incrementBy = incrementBy || 1;
let newValue = parseInt(this.getProperty(propName)); // let newValue = parseInt(this.getProperty(propName));
if (newValue) { // if (newValue) {
newValue += incrementBy; // newValue += incrementBy;
} else { // } else {
newValue = incrementBy; // newValue = incrementBy;
} // }
this.setProperty(propName, newValue); // this.setProperty(propName, newValue);
return newValue; // return newValue;
} // }
getProperty(propName) { // getProperty(propName) {
return this.properties[propName]; // return this.properties[propName];
} // }
getPropertyAsNumber(propName) { // getPropertyAsNumber(propName) {
return parseInt(this.getProperty(propName), 10); // return parseInt(this.getProperty(propName), 10);
} // }
persistProperty(propName, propValue, cb) { // persistProperty(propName, propValue, cb) {
// update live props // // update live props
this.properties[propName] = propValue; // this.properties[propName] = propValue;
return Actor.persistPropertyByActorId(this.actorId, propName, propValue, cb); // return Actor.persistPropertyByActorId(this.actorId, propName, propValue, cb);
} // }
removeProperty(propName, cb) { // removeProperty(propName, cb) {
// update live // // update live
delete this.properties[propName]; // delete this.properties[propName];
actorDb.run( // actorDb.run(
`DELETE FROM activitypub_actor_property // `DELETE FROM activitypub_actor_property
WHERE activity_id = ? AND prop_name = ?;`, // WHERE activity_id = ? AND prop_name = ?;`,
[this.actorId, propName], // [this.actorId, propName],
err => { // err => {
if (cb) { // if (cb) {
return cb(err); // return cb(err);
} // }
} // }
); // );
} // }
removeProperties(propNames, cb) { // removeProperties(propNames, cb) {
async.each( // async.each(
propNames, // propNames,
(name, next) => { // (name, next) => {
return this.removeProperty(name, next); // return this.removeProperty(name, next);
}, // },
err => { // err => {
if (cb) { // if (cb) {
return cb(err); // return cb(err);
} // }
} // }
); // );
} // }
persistProperties(properties, transOrDb, cb) { // persistProperties(properties, transOrDb, cb) {
if (!_.isFunction(cb) && _.isFunction(transOrDb)) { // if (!_.isFunction(cb) && _.isFunction(transOrDb)) {
cb = transOrDb; // cb = transOrDb;
transOrDb = actorDb; // transOrDb = actorDb;
} // }
const self = this; // const self = this;
// update live props // // update live props
_.merge(this.properties, properties); // _.merge(this.properties, properties);
const stmt = transOrDb.prepare( // const stmt = transOrDb.prepare(
`REPLACE INTO activitypub_actor_property (actor_id, prop_name, prop_value) // `REPLACE INTO activitypub_actor_property (actor_id, prop_name, prop_value)
VALUES (?, ?, ?);` // VALUES (?, ?, ?);`
); // );
async.each( // async.each(
Object.keys(properties), // Object.keys(properties),
(propName, nextProp) => { // (propName, nextProp) => {
stmt.run(self.actorId, propName, properties[propName], err => { // stmt.run(self.actorId, propName, properties[propName], err => {
return nextProp(err); // return nextProp(err);
}); // });
}, // },
err => { // err => {
if (err) { // if (err) {
return cb(err); // return cb(err);
} // }
stmt.finalize(() => { // stmt.finalize(() => {
return cb(null); // return cb(null);
}); // });
} // }
); // );
} // }
static getActor(actorId, cb) { // static getActor(actorId, cb) {
async.waterfall( // async.waterfall(
[ // [
function fetchActorId(callback) { // function fetchActorId(callback) {
Actor.getActorUrl(actorId, (err, actorUrl) => { // Actor.getActorUrl(actorId, (err, actorUrl) => {
return callback(null, actorUrl); // return callback(null, actorUrl);
}); // });
}, // },
function initProps(actorUrl, callback) { // function initProps(actorUrl, callback) {
Actor.loadProperties(actorId, (err, properties) => { // Actor.loadProperties(actorId, (err, properties) => {
return callback(err, actorUrl, properties); // return callback(err, actorUrl, properties);
}); // });
}, // },
], // ],
(err, actorUrl, properties) => { // (err, actorUrl, properties) => {
const actor = new Actor(); // const actor = new Actor();
actor.actorId = actorId; // actor.actorId = actorId;
actor.actorUrl = actorUrl; // actor.actorUrl = actorUrl;
actor.properties = properties; // actor.properties = properties;
return cb(err, actor); // return cb(err, actor);
} // }
); // );
} // }
// FIXME // // FIXME
static getActorInfo(actorId, propsList, cb) { // static getActorInfo(actorId, propsList, cb) {
if (!cb && _.isFunction(propsList)) { // if (!cb && _.isFunction(propsList)) {
cb = propsList; // cb = propsList;
propsList = [ // propsList = [
ActorProps.Type, // ActorProps.Type,
ActorProps.PreferredUsername, // ActorProps.PreferredUsername,
ActorProps.Name, // ActorProps.Name,
ActorProps.Summary, // ActorProps.Summary,
ActorProps.IconUrl, // ActorProps.IconUrl,
ActorProps.BannerUrl, // ActorProps.BannerUrl,
ActorProps.PublicKeyMain, // ActorProps.PublicKeyMain,
]; // ];
} // }
async.waterfall( // async.waterfall(
[ // [
callback => { // callback => {
return Actor.getActorUrl(actorId, callback); // return Actor.getActorUrl(actorId, callback);
}, // },
(actorUrl, callback) => { // (actorUrl, callback) => {
Actor.loadProperties(actorId, { names: propsList }, (err, props) => { // Actor.loadProperties(actorId, { names: propsList }, (err, props) => {
return callback( // return callback(
err, // err,
Object.assign({}, props, { actor_url: actorUrl }) // Object.assign({}, props, { actor_url: actorUrl })
); // );
}); // });
}, // },
], // ],
(err, actorProps) => { // (err, actorProps) => {
if (err) { // if (err) {
return cb(err); // return cb(err);
} // }
const actorInfo = {}; // const actorInfo = {};
Object.keys(actorProps).forEach(key => { // Object.keys(actorProps).forEach(key => {
actorInfo[_.camelCase(key)] = actorProps[key] || 'N/A'; // actorInfo[_.camelCase(key)] = actorProps[key] || 'N/A';
}); // });
return cb(null, actorInfo); // return cb(null, actorInfo);
} // }
); // );
} // }
static getActorIdAndUrl(actorUrl, cb) { // static getActorIdAndUrl(actorUrl, cb) {
actorDb.get( // actorDb.get(
`SELECT id, actor_url // `SELECT id, actor_url
FROM activitypub_actor // FROM activitypub_actor
WHERE actor_url LIKE ?;`, // WHERE actor_url LIKE ?;`,
[actorUrl], // [actorUrl],
(err, row) => { // (err, row) => {
if (err) { // if (err) {
return cb(err); // return cb(err);
} // }
if (row) { // if (row) {
return cb(null, row.id, row.actor_url); // return cb(null, row.id, row.actor_url);
} // }
return cb(Errors.DoesNotExist('No matching actorUrl')); // return cb(Errors.DoesNotExist('No matching actorUrl'));
} // }
); // );
} // }
static getActorUrl(actorId, cb) { // static getActorUrl(actorId, cb) {
actorDb.get( // actorDb.get(
`SELECT actor_url // `SELECT actor_url
FROM activitypub_actor // FROM activitypub_actor
WHERE id = ?;`, // WHERE id = ?;`,
[actorId], // [actorId],
(err, row) => { // (err, row) => {
if (err) { // if (err) {
return cb(err); // return cb(err);
} // }
if (row) { // if (row) {
return cb(null, row.actor_url); // return cb(null, row.actor_url);
} // }
return cb(Errors.DoesNotExist('No matching actor ID')); // return cb(Errors.DoesNotExist('No matching actor ID'));
} // }
); // );
} // }
static loadProperties(actorId, options, cb) { // static loadProperties(actorId, options, cb) {
if (!cb && _.isFunction(options)) { // if (!cb && _.isFunction(options)) {
cb = options; // cb = options;
options = {}; // options = {};
} // }
let sql = `SELECT prop_name, prop_value // let sql = `SELECT prop_name, prop_value
FROM activitypub_actor_property // FROM activitypub_actor_property
WHERE actor_id = ?`; // WHERE actor_id = ?`;
if (options.names) { // if (options.names) {
sql += ` AND prop_name IN("${options.names.join('","')}");`; // sql += ` AND prop_name IN("${options.names.join('","')}");`;
} else { // } else {
sql += ';'; // sql += ';';
} // }
let properties = {}; // let properties = {};
actorDb.each( // actorDb.each(
sql, // sql,
[actorId], // [actorId],
(err, row) => { // (err, row) => {
if (err) { // if (err) {
return cb(err); // return cb(err);
} // }
properties[row.prop_name] = row.prop_value; // properties[row.prop_name] = row.prop_value;
}, // },
err => { // err => {
return cb(err, err ? null : properties); // return cb(err, err ? null : properties);
} // }
); // );
} // }
// :TODO: make this much more flexible - propValue should allow for case-insensitive compare, etc. // // :TODO: make this much more flexible - propValue should allow for case-insensitive compare, etc.
static getActorIdsWithProperty(propName, propValue, cb) { // static getActorIdsWithProperty(propName, propValue, cb) {
let actorIds = []; // let actorIds = [];
actorDb.each( // actorDb.each(
`SELECT actor_id // `SELECT actor_id
FROM activitypub_actor_property // FROM activitypub_actor_property
WHERE prop_name = ? AND prop_value = ?;`, // WHERE prop_name = ? AND prop_value = ?;`,
[propName, propValue], // [propName, propValue],
(err, row) => { // (err, row) => {
if (row) { // if (row) {
actorIds.push(row.actor_id); // actorIds.push(row.actor_id);
} // }
}, // },
() => { // () => {
return cb(null, actorIds); // return cb(null, actorIds);
} // }
); // );
} // }
static getActorCount(cb) { // static getActorCount(cb) {
actorDb.get( // actorDb.get(
`SELECT count() AS actor_count // `SELECT count() AS actor_count
FROM activitypub_actor;`, // FROM activitypub_actor;`,
(err, row) => { // (err, row) => {
if (err) { // if (err) {
return cb(err); // return cb(err);
} // }
return cb(null, row.actor_count); // return cb(null, row.actor_count);
} // }
); // );
} // }
}; };

View File

@ -177,7 +177,7 @@ exports.getModule = class ActivityPubWebHandler extends WebHandlerModule {
return this.webServer.resourceNotFound(resp); return this.webServer.resourceNotFound(resp);
} }
Actor.getRemoteActor(activity.actor, (err, actor) => { Actor.fromRemoteUrl(activity.actor, (err, actor) => {
if (err) { if (err) {
// :TODO: log, and probably should be inspecting |err| // :TODO: log, and probably should be inspecting |err|
return this.webServer.internalServerError(resp); return this.webServer.internalServerError(resp);