Skip to content

Instantly share code, notes, and snippets.

@amonks
Last active June 7, 2020 07:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save amonks/9916961cc8ec9ba12013b9d4315cfe85 to your computer and use it in GitHub Desktop.
Save amonks/9916961cc8ec9ba12013b9d4315cfe85 to your computer and use it in GitHub Desktop.
// Add something here to handle a new type of plex event.
const event_handlers = {
"media.resume": silent,
"media.pause": silent,
"media.scrobble": silent,
"media.play": verb_handler("started playing"),
"media.stop": silent,
};
// silent handles a plex event by posting no message to rocket chat.
function silent(event) {
return undefined;
}
// handle_media_event makes a robot messsage describing a plex event. It uses a
// cache to avoid sending the same message about the same user twice in a row.
function handle_media_event(verbed, event) {
const username = user_name(event);
const message = make_robot_message(
`${username} ${verbed} ${media_name(event)}`
);
const messageString = JSON.stringify(message);
const mostRecentMessageStringForThisUser =
most_recent_messages_for_users[username];
// Do not send a message if we already sent the same message for this user.
if (messageString === mostRecentMessageStringForThisUser) {
return undefined;
}
// Otherwise, set _this_ as the most recent event and send it out.
most_recent_messages_for_users[username] = messageString;
return message;
}
// We'll store in this cache a map from a user to the (stringified) most recent
// message we sent about that user. We'll use it to avoid sending the same
// message more than once in a row.
const most_recent_messages_for_users = {};
// user_name is the name of the user that triggered the plex event.
function user_name(event) {
return event.Account.title;
}
// media_name is a nicely formatted name for the media referenced by a plex
// event.
function media_name(event) {
const metadata = event.Metadata;
const { title, grandparentTitle, parentTitle } = metadata;
const possibleTitles = [grandparentTitle, parentTitle, title];
const titlesThatExist = possibleTitles.filter(Boolean);
return titlesThatExist.join(" • ");
}
// verb_handler makes an event handler for a particular verb.
function verb_handler(verb) {
return function (event) {
return handle_media_event(verb, event);
};
}
// handle_plex_request, given a parsed request from plex, returns a message to
// send to the #plexhooks channel in robot, or `undefined` to send no message.
function handle_plex_request(plexRequest) {
const eventType = plexRequest.event;
try {
const handler = event_handlers[eventType];
if (!handler) {
throw Error(`No handler defined`);
}
return handler(plexRequest);
} catch (e) {
return make_robot_message(
`Error handling ${codeblock(eventType)}: ${e}`,
plexRequest
);
}
}
// make_robot_message builds a robot message, optionally including a nice
// pretty code block with some metadata afterwards.
function make_robot_message(text, metadata) {
if (!metadata) {
return { content: { text } };
}
return {
content: {
text: text + multiline_code_block(prettify(metadata)),
},
};
}
// codeblock wraps a string in backticks.
function codeblock(str) {
return "`" + str + "`";
}
// multiline_code_block wraps a string in lines with triple-backticks.
function multiline_code_block(str) {
const codeblock = "\n```\n";
return codeblock + str + codeblock;
}
// prettify turns an object into nice pretty multiline indented json.
function prettify(data) {
return JSON.stringify(data, null, 2);
}
// `data` includes many parts separated by a boundary string. One of those
// parts is json. We'll assume that the first part containing a "{" is the
// json part, and parse that.
function parse_json_from_first_part_of_multipart_form_data(data) {
const multipartBoundary = "--------------------------";
const parts = data.split(multipartBoundary);
const jsonPart = parts.find((part) => part.includes("{"));
const json = jsonPart.slice(
jsonPart.indexOf("{"),
jsonPart.lastIndexOf("}") + 1
);
return JSON.parse(json);
}
class Script {
process_incoming_request({ request }) {
try {
// robot.chat tries to parse json content into a property
// `request.content`, but it does not succeed because of plex's
// weird-looking requests, so we will parse it ourselves.
const rawContent = request.content_raw;
const content = parse_json_from_first_part_of_multipart_form_data(
rawContent
);
return handle_plex_request(content);
} catch (e) {
return make_robot_message(`Error handling plex webhook "${e}"`, request);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment