Discord integration either via automation or clicks

edited March 17 in Feature Requests

So based on a convo in OPs discord I came up with a couple of snippets that can be used to create a button, scrape some data, and then send that to discord. Aside from being pretty specific in what it does and in no way built with any error handling thus far, the real biggest issue is that it requires you put a full webhook URL in your sites publicly visible source.

That got me thinking about some level of official support for this idea. Even if it was just in the form of a place to add webhook URL(s) and then a method by which you can invoke them without having to make that publicly available.

Without further ado, here are the JS snippets I have thus far:

//This injects a button after the Character Name on a character page
//I have done virtually no testing so I cannot say this won't affect other pages yet
if(document.getElementsByClassName('character-name').length > 0
&& !document.getElementById("charDiscordButton")){
var charDiscordButton= document.createElement("button");
charDiscordButton.textContent = 'Send to Discord';
onClickString = buildOnClickDiscordString('character');
charDiscordButton.setAttribute("onClick", onClickString);
charDiscordButton.setAttribute("id", "charDiscordButton");

document.getElementById("charDiscordButton").className += " button";

//This injects to all wiki pages based on tags I use on my site for the switch
//Update this to whatever you might need to determine how you'd like to parse different scenarios
//In my scenario each of these are mutually exclusive
if(document.getElementsByClassName('tag-link').length > 0
&& !document.getElementById("wikiDiscordButton")){
var wikiDiscordButton= document.createElement("button");
wikiDiscordButton.textContent = 'Send to Discord';
for (let i = 0; i < document.getElementsByClassName('tag-link').length; i++) {
let tag = document.getElementsByClassName('tag-link')[i].innerHTML;
switch (tag) {
case "monsters":
wikiType = "monster";
case "city":
wikiType = "city";
case "faction":
wikiType = "faction";
case "holiday":
wikiType = "holiday";
wikiType = "wiki";

onClickString = buildOnClickDiscordString(wikiType);
wikiDiscordButton.setAttribute("onClick", onClickString);
wikiDiscordButton.setAttribute("id", "wikiDiscordButton");

let referenceNode = document.getElementsByClassName("see-all-wiki-pages-button button")[0];
referenceNode.parentNode.insertBefore(wikiDiscordButton, referenceNode.nextSibling);
document.getElementById("wikiDiscordButton").className += " button";

//This accepts a string source(only the one for now) and then
//builds a payload for the actual sending
function buildOnClickDiscordString(source){
let startingMessage = "sendMessage(";
switch (source) {
case "character":
let title = document.getElementsByTagName("title")[0].innerHTML;
let position = title.indexOf("|")-1;
let charName = title.substring(0, position);
let imgSource = document.querySelector("[title='"+charName+"']").src;
let charParent = document.getElementsByClassName('description')[0];
let charBody = charParent.querySelectorAll('p')[0].innerHTML;
return startingMessage + "'" + imgSource + " " + charName + " | " + charBody + "');";
case "monster":
let monsterName = document.getElementsByTagName('h1')[1].innerHTML;
let allImages = document.getElementsByTagName('img');
var monsterImg = '';
for (let i = 0; i < allImages.length; i++) {
if (allImages[i].className == 'media-item-align-center') {
monsterImg = allImages[i].src;
let monsterFirstSentence = document.getElementsByClassName('grid-item')[0].querySelectorAll('p')[0].innerHTML.split('.')[0];
return startingMessage + "'" + monsterImg + " " + monsterName + " " + monsterFirstSentence + "');";
case "city":
let cityDescriptionBlurb = document.getElementsByClassName('grid-item')[0].querySelectorAll('p')[0].innerHTML.substr(0,1800).trimEnd().replace(/(<([^>]+)>)/gi, "");
return startingMessage + "'" + window.location.href + " " + cityDescriptionBlurb + "...');";
case "holiday":
let allParagraphs = document.getElementsByTagName('p');
var holidayDescriptionBlurb = '';
for (let i = 0; i < allParagraphs.length -1; i++) {
holidayDescriptionBlurb += allParagraphs[i].innerHTML.replace(/(<([^>]+)>)/gi, "");
return startingMessage + "'" + window.location.href + " " + holidayDescriptionBlurb + "...');";
case "wiki":
return startingMessage + "'" + window.location.href + "');";
return startingMessage + "'ERROR'); return false;";
console.log('no source specified');

//Lastly this function handles the actual sending to discord
//This is also the part where your discord webhook URL would be made public as it stands now
//afaik there is no way for us to get around this without something official
function sendMessage(messageText) {
var data = JSON.stringify({
"content": messageText

var xhr = new XMLHttpRequest();
var url = 'https://discord.com/api/webhooks/xxx';

xhr.open('POST', url, true);
xhr.setRequestHeader("Content-Type", "application/json");



Post edited by nuadaria on


  • R_Panda
    Posts: 8

    I'm also quite interested in this feature.

  • nuadaria
    Posts: 36

    OK, so I may have gotten further down the rabbit hole with this today. Here's a gist for anyone interested. All of this is still VERY MUCH a rapid prototype and hyper specific to my campaign so you will certainly need some knowledge of JavaScript or some great googleFu.


  • Vanillabean
    Posts: 59

    Thanks for the thoughts! I'll pass this on to the team for sure!

    Obsidian Portal Support Sorcerer

    [email protected] 

  • nuadaria
    Posts: 36

    Awesome, thanks!

  • thaen
    Posts: 983

    @nuadaria, this is pretty cool!!

    Even if it was just in the form of a place to add webhook URL(s) and then a method by which you can invoke them without having to make that publicly available.

    Let me see if I'm on the same page with what you need here ...

    OP would have a place, probably in the Campaign Settings Advanced tab, where you enter webhook URLs. To keep it simple to implement, maybe it's just a text box like Custom CSS, and each webhook URL needs to be on it's own line.

    Then your Campaign's JavaScript could request the webhook URL by making a GET to something like "https://campaign-slug.obsidianportal.com/webhook/1", where "1" is the first webhook URL in the list, and so on for subsequent webhook URLs.

    When your Campaign's JavaScript GETs that OP webhook URL, the OP server would check that the visitor was a logged in member of the Campaign. If the visitor wasn't logged in or wasn't a member of the Campaign, then the GET result would just be empty.

    On the other hand, if the visitor is logged in and is a member of the Campaign, then the OP server would go ahead and return the first webhook URL in the list, and then the Campaign's JavaScript can go ahead and use that returned webhook URL to POST to Discord.

    Does that match what you're thinking?

    Obsidian Portal Developer

  • thaen
    Posts: 983

    On second thought, rather than making this "webhook specific", we would probably just add a "Custom JavaScript - Private" textbox. And that would be additional JavaScript that only logged in Campaign members would get. So, then you could put whatever URLs or data or functions or whatever in a JavaScript object that you could reference in your regular "Custom JavaScript".

    Obsidian Portal Developer

  • nuadaria
    Posts: 36

    That's not exactly how I had imagined it but that seems to allow for verification before someone is given access to the URL and seems prolly lighter weight from your perspective. That should work for me.



  • nuadaria
    Posts: 36

    OOO, that is even easier for you I bet. I love it!

  • thaen
    Posts: 983

    Actually, if we do that, which would be very easy, you won't even have to change your code ... you would just move it all over to the "Private" side, and then it will only show up for your Campaign members.

    I've added this to the list of feature requests.

    Obsidian Portal Developer

  • nuadaria
    Posts: 36

    Fantastic. My wheels are already turning for other fun stuff I might be able to do.

Sign In or Register to comment.

March 2023

Read the feature post on the blog
Return to Obsidian Portal

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!