Link rewriting for affiliate codes

I saw this and was thrilled:

Then I installed it, and realized that it still only works for Amazon. Would it be hard to add support for ShareASale? Maybe even a “generic” rewrite where you regex an URL and do a replacement?

2 Likes

I know this is from '16 but I can’t find an answer to this and looking for something similar. Is there a plugin that allows a ‘generic’ rewrite of a URL submitted by community members i.e. add an affiliate tag or change the link from a user submitted one to an affiliate one?

Not that I know, but I’m interested to hear if there’s been developments with regards to this :slight_smile:

This should possible in a theme component these days, depending on what the links should look like.

Can you post an example of the base link and an example of what it would look like as an affiliate link?

2 Likes

This discussion has prematurely ended. I may need to re-think the monetization of our community.

In my case I would need to add a prefix to an URL.

http://merchantxyz.com/....

and I would need to convert that into…

https://tracker.adagency123.com/t/t?a=12345678&as=87654321&t=2&tk=1&url=https://merchantxyz.com/...

There are several topics that touch this issue, but was there actually a solution ready for production?

2 Likes

Unfortunately I don’t think so. Likely this would be a great candidate for a #marketplace job.

2 Likes

Would this be possible to implement as theme customization. I am looking at modifying this JSFiddle, but what would be the correct way to implement this to a Discourse instance – if at all possible?

// Change "my-affiliate-id" below to your actual affiliate id
const aid = 'my-affiliate-id';

// Append slash with affiliate id, only if an affiliate ID is not found in the link yet
const goglinks = document.querySelectorAll('a[href*="gog.com"]');
goglinks.forEach(function(el) {
  if(!el.href.includes('pp=')) {
    el.href = el.href.replace(/\?.*$/, '') + '?pp=' + aid
  }
})
1 Like

Yes, it’s possible. Let’s start with something simple. First, you add this to the header tab of a new theme component.

<script type="text/discourse-plugin" version="0.8.42">

</script>

What does this do? It’s a special type of script tags that get handled by Discourse. What’s the benefit? It allows you to access the plugin API methods.

You want to append an affiliate code to some links. So, that means that you want to modify the contents of posts. If you check the plugin API, you’ll see that there is indeed a method for that.

https://github.com/discourse/discourse/blob/master/app/assets/javascripts/discourse/app/lib/plugin-api.js#L224-L262

You use it like this.

api.decorateCookedElement(
  element => {
    // do your work here. 
    // element passed here is the HTML node for cooked post
  },
  {
    // options go here
  }
);

This is the wrapper that you need to put your code in to have it fire when a post is rendered. So, let’s try that.

We take your code and add it as is

api.decorateCookedElement(
  element => {
++  // Change "my-affiliate-id" below to your actual affiliate id
++  const aid = "my-affiliate-id";
++
++  // Append slash with affiliate id, only if an affiliate ID is not found in the link yet
++  const goglinks = document.querySelectorAll('a[href*="gog.com"]');
++  goglinks.forEach(function (el) {
++    if (!el.href.includes("pp=")) {
++      el.href = el.href.replace(/\?.*$/, "") + "?pp=" + aid;
++    }
++  });
  },
  {
    // options go here
  }
);

Does this work? Yes, but it’s also doing some redundant work since we’re querying the document for the links instead of the post element. So, how do you fix that?

Remember the element argument that we passed in the method? Well, this is when it comes in handy.

Instead of querying the document, we query the element we want to target. So, we change this.

const goglinks = document.querySelectorAll('a[href*="gog.com"]');

to this

const goglinks = element.querySelectorAll('a[href*="gog.com"]');

and here’s what we end up with

api.decorateCookedElement(
  element => {
    // Change "my-affiliate-id" below to your actual affiliate id
    const aid = "my-affiliate-id";

    // Append slash with affiliate id, only if an affiliate ID is not found in the link yet
--  const goglinks = document.querySelectorAll('a[href*="gog.com"]');
++  const goglinks = element.querySelectorAll('a[href*="gog.com"]');
    goglinks.forEach(function (el) {
      if (!el.href.includes("pp=")) {
        el.href = el.href.replace(/\?.*$/, "") + "?pp=" + aid;
      }
    });
  },
  {
    // options go here
  }
);

This works great now, but we can still improve it a little bit. Since you’re only adding an affiliate id to links, you don’t need this to fire when you’re in the composer. That’s where the method options come in handy.

One of the options you can pass to the method is onlyStream

What does it do? It tells the method that you only want this code to fire when the post is rendered inside a topic. Let’s add that option.

api.decorateCookedElement(
  element => {
    // Change "my-affiliate-id" below to your actual affiliate id
    const aid = "my-affiliate-id";

    // Append slash with affiliate id, only if an affiliate ID is not found in the link yet
    const goglinks = element.querySelectorAll('a[href*="gog.com"]');
    goglinks.forEach(function (el) {
      if (!el.href.includes("pp=")) {
        el.href = el.href.replace(/\?.*$/, "") + "?pp=" + aid;
      }
    });
  },
  {
++  onlyStream: true
  }
);

So, now the only thing left to do is put this code in the special script tag we talked about earlier.

<script type="text/discourse-plugin" version="0.8.42">
api.decorateCookedElement(
  element => {
    // Change "my-affiliate-id" below to your actual affiliate id
    const aid = "my-affiliate-id";

    // Append slash with affiliate id, only if an affiliate ID is not found in the link yet
    const goglinks = element.querySelectorAll('a[href*="gog.com"]');
    goglinks.forEach(function (el) {
      if (!el.href.includes("pp=")) {
        el.href = el.href.replace(/\?.*$/, "") + "?pp=" + aid;
      }
    });
  },
  {
    onlyStream: true
  }
);
</script>

Then add it to the header tab of your component, and you’re all set. I used GOG in this example because that’s what you asked about, but this pattern can be used for any affiliate program as long as you know the URL structure.

11 Likes

Whoa, what a fantastic and educational tutorial!

The JSFiddle was an just an example I found. This new potential partner of ours uses a https:// -prefix, rather than and affiliate ID at the end. They use the adtraction.com platform.

Posting my modified version here for others to exploit.


<script type="text/discourse-plugin" version="0.8.42">
api.decorateCookedElement(
  element => {
        const affiliate = 'https://their-ad-network.com/...';
        const affiliate_links = element.querySelectorAll('a[href*="potentialparterwebsite.com"]');

        affiliate_links.forEach(function(el) {
            if (!el.href.startsWith(affiliate)) {
                el.href = affiliate +  encodeURIComponent(el.href);
            }
        });
    },
  {
    onlyStream: true
  }
);
</script>

So now the code ads the ad networks URL first, that tracks the clicks, and then appends with partner link.

3 Likes

While this works elegantly for links that are made manually, by linking a word to https://website, it doesn’t seem catch links that are automatically generated by Discourse’s parser. In other words:

This works: Google
But this does not: Google.com

Can the script be improved to also catch the automatically generated links?