-
Notifications
You must be signed in to change notification settings - Fork 16
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
Allow 'Exceptions' For Kill Count Notifier #482
Comments
Possible syntax
If Longer example
but if the same boss is listed twice with different intervals, it's unclear which to use |
This setting may be too complicated to accurately explain to users Since you have programming experience, I would recommend setting your interval to 1 and write code for a custom webhook handler to forward the notification if your boss-specific interval is met |
Thanks for the information. This may be a good opportunity for me to explore plugin development! Do you have any recommended guides/docs for creating custom webhooks? |
To clarify, rather than writing runelite plugin code, you can write a HTTP request handler that accepts Dink's POST requests and performs custom logic. If you set your kc interval to 1, every kc would hit your logic, which can selectively forward the notification to discord webhook URLs depending on whether the kc is divisible by arbitrary boss-specific values. To host your webhook handler for free, you can use Cloudflare Workers or AWS Lambda or a localhost webserver. To parse Dink requests, I recommend you read these docs: https://github.com/pajlads/DinkPlugin/blob/master/docs/json-examples.md#structure |
Thank you for the clarification. I'm making good progress! I was wondering if there was some documentation for boss names? I.e. will Dink send me |
Glad to hear! For the kill count plugin, it uses the exact name used in the chat message. The main exception to this is (Corrupted) Gauntlet: we report the NPC name instead (i.e., Crystalline Hunllef or Corrupted Hunllef). For your two examples, |
Appreciate the information! I am getting a bit stuck on creating the img url to where when it sends to the webhook it posts the screenshot from Dink. Do you have any tips on how to tackle this? Does the img actually have to be hosted somewhere? |
Ignoring the heresy that is my snake case, this is my node.js running express + multer to handle dink requests. I've stripped out the surrounding code, this should hopefully point you in the right direction. This link has a lot of great documentation: https://expressjs.com/en/resources/middleware/multer.html //Require express and multer (for multi-part webhook/API body handling)
const express = require("express");
const multer = require("multer");
//Initialize express as webhook_handler and define a port
const webhook_handler = express();
const PORT = 1234;
//Start express on the defined port
webhook_handler.listen(PORT, () => console.log(`🚀 Webhook Listening-Server running on port ${PORT}.`));
webhook_handler.use(express.json());
//Multer diskstorage for image up/down load to local
const storage = multer.diskStorage({
destination: './webhook_files',
filename: function (req, file, cb) {
cb(null, path.parse(file.originalname).name + "-" + Date.now() + path.extname(file.originalname));
}
});
//Multer file filter for extention
const file_filter = function (req, file, cb) {
const allowed_mimes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'];
if (allowed_mimes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(null, false);
}
};
//upload called when file receieved on webhook, file size limit and filter
//maximum file size is 15Mb
const upload = multer({
storage: storage,
limits: { fileSize: 15 ** 7 },
fileFilter: file_filter
}).single('file');
|
Once you handle all of this and build some infrastructure around your webhook_handler, when you get up to the handling of KC (I had the same use case) it's very simple: //Assume request is parsed to this class on API call, which includes the whole incoming request, body, imagepath etc
const content = request.body;
//Set payload to JSON parsed payload_json from body.
const payload = JSON.parse(content.payload_json);
//Create type, player_name, account_type const, from above payload information.
const type = payload.type;
const player_name = payload.playerName;
const account_type = payload.accountType;
//Create extra const, from supplied JSON metadata from 'Dink'
const extra = payload.extra;
//File handling
const image_name = request.file ? request.file.filename : null;
const image_path = request.file ? request.file.path : null;
//Prepare generic event_embed (used for all types).
const event_embed = new Discord.EmbedBuilder()
.setColor("#ad4234")
.setAuthor({ name: author_name, iconURL: payload.embeds[0].author.icon_url, url: payload.embeds[0].author.url })
.setTimestamp()
//THIS STEP IS IMPORTANT, if the user did not submit an image, you want to account for that
//Check if image was submitted -> if image, set the attachment to the image.
if(image_path){
event_embed.setImage(`attachment://${image_name}`);
}
//HANDLE KILL COUNT (BOSSING)
if(payload.type === "KILL_COUNT"){
const boss_kc = extra.count;
const remainder_by_5 = extra.count % 5;
const remainder_by_50 = extra.count % 50;
if(boss_kc < 5){
//OPTIONAL MESSAGE
}else if(boss_kc < 50 && remainder_by_5 === 0){
//OPTIONAL MESSAGE
}else if(remainder_by_50 === 0){
//OPTIONAL MESSAGE
}else{
//Otherwise, no condition above matched, so delete the file if any and return.
if(image_path) //handle deletion of image from server (free up storage space)
return;
}
event_embed
.setDescription(`${source.aquire_type} **[${extra.boss}](${source.wiki_URL}})** for the ${extra.count}${utils_formatting.ordinal(extra.count)} time!`)
.setThumbnail('https://oldschool.runescape.wiki/images/Multicombat.png')
} |
you can also simplify this by just reusing the incoming request so you don't need to save the image to file or bother with discord.js if (payload.type === "KILL_COUNT") {
const boss = extra.boss;
const kc = extra.count;
if (isNoteworthyKc(boss, kc)) {
return await fetch(env["url"], request);
}
} |
I would think this approach would work for me with fastify as well but something as simple as -
but I'm having no luck. I'm leaning towards the issue being with the body and how Discord is expecting a I've tried with Dink sending the request and with Postman. Here is the log from Postman -
|
don't think you can pass fastify's Request object as a |
oh actually code 307 won't work because runelite uses an ancient okhttp version ( |
if you're willing to wait, we can add logic to dink to workaround that okhttp bug |
I went ahead and started looking down this approach but testing with Postman for the time being. It does work however, if an image is sent with the redirect I get error No img redirect ->Posts -> No img redirect -> Posts -> img redirect -> error Pretty clear the image has something to do with it but I can't seem to find any similar situations online. |
@jdanthdavis could you share your full js code? this problem is outside the realm of dink (can't replicate using a cloudflare worker), but maybe one of us can spot where things are going wrong |
@jdanthdavis sample cloudflare worker handler https://gist.github.com/iProdigy/53654691a76679221a74fd442dea068a |
Here is my current Cloudflare Worker (would love feedback if you're not busy) - https://gist.github.com/jdanthdavis/647ab73453373f2381f357c9652b58b7 After reviewing yours I was able to get Postman to work flawlessly! Of course, an attempt with Dink didn't succeed but I'm sure your fix will address that. Here is the Cloudflare log of the Dink post in case it could be helpful for you. At this point I think we just wait til the fix is published. I really appreciate your help @iProdigy and @APKiwi! It was my first time working with webhooks and workers and it was awesome to learn.
|
@jdanthdavis glad to hear! in terms of code review:
lastly, since you're on cf workers now, if you don't want to wait for the okhttp patch, you can switch back to |
@iProdigy thanks for the feedback; some really good catches their as well! I went ahead and made the adjustments - https://gist.github.com/jdanthdavis/647ab73453373f2381f357c9652b58b7 It is fully working now as intended. However, I did receive this Again, greatly appreciate all the help with this! |
@iProdigy Quick question, we ran into an instance where the special occasion logic triggered and we saw some unexpected results. A player got their first KC but it also was their personal best since it's their first completion. However, the player double-deathed with the boss. What's interesting is that we had the message for a personal best but it was sent to our I've setup a fiddle with manipulated data and it works as intended. Did we see this because Dink would be sending two different payloads for this scenario? I.e it sent the personal best payload first and then it would have sent the kc payload after? I'm just confused on how we received the dink message for a personal best but the |
Your logic has: So |
It's a single kill so dink sends 1 kc notif The forwarded message is missing the specific kc because the user has modified the |
I see. We achieved a PB so dink sends the PB message but we modified our message to not include So, to achieve the logic/messages I want I would have to actually modify the message from Dink once I've done my logic? Which won't be possible until your PR is merged if I'm not mistaking. |
you don't need the 307/308 pr to fix this The easiest approach is to just have users update the value of Alternatively, you can use the values in the |
100% is the easiest solution for this. This is definitely a super picky scenario (plus learning, ya know?)
I am able to deconstruct everything from My next approach will be to review the logs of my worker to see what the |
yeah the discord docs may not be so clear:
cloudflare's real-time logs do report |
Happy to say with your help we're making wonderful progress. We're getting successful posts with Dink with the custom messages. Albeit, I'm currently passing a random
I also couldn't find the property names of file in the Dink docs either. Just for reference on how I'm building the
|
you should use then:
|
I really appreciate your help! With mock data we're looking good. Getting each custom message sent to it's corresponding URL. If you have the time I'd love some feedback on my approach. https://gist.github.com/jdanthdavis/647ab73453373f2381f357c9652b58b7 |
yw! I'd add a null check before appending may also want to consider a try/catch in case one request fails (which would cause dink to attempt a repeated notif) |
Made those adjustments! We did just have an instance where a PB was obtained in game but for the time we posted In the case I do need to clean the string myself; do you have an example of what |
yeah we send with precise timing, it'd look like one trick is to remove |
Seems to be working flawlessly with mock data! Went with this solution - |
Hey @iProdigy, noticed something a bit weird with the payload for HMT PBs. We got a 5 man PB but I got EDIT: Looks like I'm getting send my "Theatre of Blood total completion time" when the "Theatre of Blood completion time" was the new PB. |
Could you screenshot the game tab where it says new personal best (rather than clan tab) |
Dink uses the last time posted in chat, so it's reporting |
Checklist
Unreleased
section of the changelog for newly added features that sound like my suggestion.Describe your Suggestion
Allow users to enter exceptions that doesn't follow the
Kill Count Interval
value; similar toIgnored Region IDs
in the Death setting. Whether this setting would just send a notification for every time the boss is killed or if possible allow the user to also define what theKill Count Interval
would be for that exception.Reasoning
This would be useful in situations where a group has their
Kill Count Interval
set to100
to avoid spam notifications for the average boss but being able to setKill Count Interval
to say 5 for Zuk/Colosseum would be a nice to have.The text was updated successfully, but these errors were encountered: