IdiNium Posted April 16, 2020 Report Posted April 16, 2020 (edited) Hello, I am new to the coding world, and I have problem that confuses me on the utilizing of getUserDetails function. I've read about this here - getUserDetails but I am not sure how to make it happen actually. Here's my attempt on doing it on tf2-automatic trading bot, which I've successfully added the Discord Webhook Integration feature, but the only missing things is that the trade partner Avatar and Name. The webhook function has two, which it sends a successful trade summary and an offer that need to be reviewed. The picture attached is only for function that sends to discord for reviewing offer. I also tried creating a function to be called, but not shown in the picture (but in the file). Language used: TypeScript. If you don't mind trying to edit it for me, you can git clone my forked repo that I'm currently been working on this: git clone https://github.com/idinium96/tf2-automatic.git && git checkout partner-avatar-and-name && npm install and open the file MyHandler.ts in tf2automatic/src/classes/Myhandler.ts Thank you and I appreciate your help regarding this matters. I've edit the git repo to the branch that related to this topic. Edited April 16, 2020 by IdiNium Quote
Dr. McKay Posted April 16, 2020 Report Posted April 16, 2020 I don't understand your question. What exactly isn't working? Quote
IdiNium Posted April 16, 2020 Author Report Posted April 16, 2020 Sorry if my question not quite understandable, but is it my usage of getUserDetails to get partnerAvatar and partnerName is correct? Quote
vrtgn Posted April 16, 2020 Report Posted April 16, 2020 (edited) Your usage of getUserDetails is correct. Here is the documentation for it. There are multiple problems: You are making the variable partnerAvatar and partnerName equal to a function (also known as a void). The error you are receiving is that the logger cannot log functions. You are trying to assign the value of those variables by returning a value from the callback function which is incorrect. You are attempting to log the values outside the callback. You are calling offer.getUserDetails twice which is bad practice. Firstly, you can't log functions, there is no need to, and that is not even what you are trying to do. Secondly, you can assign variables to functions and call them (here are some different ways to make functions), but you can't assign a value to that variable (as it is already a function) from that function. Thirdly, you need to have an understanding of how callbacks work. Basically, when you call offer.getUserDetails it sends steam a request which takes time. When it gets the response, the offer class callbacks to that function. If you log instantly after the callback it will log it as null/undefined. Instead you need to log inside the callback when the details have been received. Finally, if you have already called offer.getUserDetails, there is no reason to call it again. Just get the variables from the first call. This is my solution which would be there instead of lines 1020 - 1056. offer.getUserDetails(function(err, me, them) { if (err) { // handle error, log.debug(err), etc. } else { const partnerAvatar = them.avatarFull; const partnerName = them.personaName; log.debug(partnerAvatar); log.debug(partnerName); const stringified = JSON.stringify(discordReviewOfferSummary) .replace(/%partnerId%/g, partnerSteamID) .replace(/%partnerName%/g, '//Coming Soon//') .replace(/%partnerAvatar%/g, partnerAvatar) .replace(/%offerId%/g, offer.id) .replace(/%reason%/g, reason) .replace(/%tradeSummary%/g, offer.summarize(this.bot.schema).replace('Offered:', '\\n Offered:')) .replace(/%ownerDiscordId%/g, process.env.OWNER_DISCORD_ID) .replace(/%currentTime%/g, moment().format('MMMM Do YYYY, HH:mm:ss') + ' UTC'); const jsonObject = JSON.parse(stringified); request.send(JSON.stringify(jsonObject)); log.debug('Review offer summary sent to webhook'); } }) Hope this helps, and if I'm wrong feel free to correct me Edited April 16, 2020 by vrtgn Quote
IdiNium Posted April 16, 2020 Author Report Posted April 16, 2020 Hello @vrtgn, Thank you for your time and effort explaining to me how it should be working and etc! I really don't know that a variable is not assignable to a function. At first I thought the function will return as a value to the variable, but in reality it's not. I'll keep that in mind and learn more about this. About the code you've shared to me, it's working with some small replacement made and yes, it really is working! Here's the one that is working: private sendWebHookReviewOfferSummary(offer: TradeOfferManager.TradeOffer, reason: string): void { const request = new XMLHttpRequest(); request.open('POST', process.env.DISCORD_WEBHOOK_REVIEW_OFFER_URL); request.setRequestHeader('Content-type', 'application/json'); const partnerSteamID = offer.partner.toString(); const tradeSummary = offer.summarize(this.bot.schema); // I replace this to here, because "this" will be undefined if placed as before let partnerAvatar; // I declare variables before the function let partnerName; log.debug('getting partner Avatar and Name...'); offer.getUserDetails(function(err, me, them) { if (err) { partnerAvatar = 'https://p7.hiclipart.com/preview/313/980/1020/question-mark-icon-question-mark-png.jpg'; // Giving it "?" image. partnerName = 'unknown'; // Set it to unknown } else { partnerAvatar = them.avatarFull; partnerName = them.personaName; } const stringified = JSON.stringify(discordReviewOfferSummary) .replace(/%partnerId%/g, partnerSteamID) .replace(/%partnerName%/g, partnerName) .replace(/%partnerAvatar%/g, partnerAvatar) .replace(/%offerId%/g, offer.id) .replace(/%reason%/g, reason) .replace(/%tradeSummary%/g, tradeSummary.replace('Offered:', '\\n Offered:')) .replace(/%ownerDiscordId%/g, process.env.OWNER_DISCORD_ID) .replace(/%currentTime%/g, moment().format('MMMM Do YYYY, HH:mm:ss') + ' UTC'); const jsonObject = JSON.parse(stringified); request.send(JSON.stringify(jsonObject)); }); log.debug('Review offer summary sent to webhook'); } But, then, when I try to accept the trade, and this function should be called when it accept the trade: private sendWebHookTradeSummary(offer: TradeOfferManager.TradeOffer): void { const request = new XMLHttpRequest(); request.open('POST', process.env.DISCORD_WEBHOOK_TRADE_SUMMARY_URL); request.setRequestHeader('Content-type', 'application/json'); const partnerSteamID = offer.partner.toString(); const tradeSummary = offer.summarize(this.bot.schema); let tradesTotal = 0; const offerData = this.bot.manager.pollData.offerData; for (const offerID in offerData) { if (!Object.prototype.hasOwnProperty.call(offerData, offerID)) { continue; } if (offerData[offerID].handledByUs === true && offerData[offerID].isAccepted === true) { // Sucessful trades handled by the bot tradesTotal++; } } const tradesMade = process.env.TRADES_MADE_STARTER_VALUE ? +process.env.TRADES_MADE_STARTER_VALUE + tradesTotal : 0 + tradesTotal; let partnerAvatar; let partnerName; log.debug('getting partner Avatar and Name...'); offer.getUserDetails(function(err, me, them) { if (err) { log.debug('Error retrieving partner Avatar and Name: ', err); partnerAvatar = 'https://p7.hiclipart.com/preview/313/980/1020/question-mark-icon-question-mark-png.jpg'; partnerName = 'unknown'; } else { log.debug('partner Avatar and Name retrieved. Applying...'); partnerAvatar = them.avatarFull; partnerName = them.personaName; } const stringified = JSON.stringify(discordTradeSummary) .replace(/%partnerId%/g, partnerSteamID) .replace(/%partnerName%/g, partnerName) .replace(/%partnerAvatar%/g, partnerAvatar) .replace(/%offerId%/g, offer.id) .replace(/%tradeNum%/g, tradesMade.toString()) .replace(/%tradeSummary%/g, tradeSummary.replace('Offered:', '\\n Offered:')) .replace(/%currentTime%/g, moment().format('MMMM Do YYYY, HH:mm:ss') + ' UTC'); const jsonObject = JSON.parse(stringified); request.send(JSON.stringify(jsonObject)); }); log.debug('Accepted trade summmary sent to webhook'); } Which I believe it shouldn't have any problem, because it's almost identical to the sendWebhookReviewOfferSummary, but on the Discord I've always received this: And when I check on the log, this is the error that it thrown: Is that mean I have to use another function to get partnerAvatar and partnerName from the trade that has been already been accepted? Again, thank you and I really appreciate your help! Quote
vrtgn Posted April 16, 2020 Report Posted April 16, 2020 (edited) 2 hours ago, IdiNium said: Thank you for your time and effort explaining to me how it should be working and etc! I really don't know that a variable is not assignable to a function. At first I thought the function will return as a value to the variable, but in reality it's not. I'll keep that in mind and learn more about this. Happy to help, but note that a function is assignable to a variable; you just can't return that value from the function. You will progress as you learn. I'll be using callbacks below so here is a tutorial/explanation if you still don't entirely understand. 2 hours ago, IdiNium said: And when I check on the log, this is the error that it thrown: Is that mean I have to use another function to get partnerAvatar and partnerName from the trade that has been already been accepted? I'm not entirely sure how the Discord webhooks work, I myself have never used them. But from what it seems is that if an offer is active you can get the details of the users, however if it is not, then you will have to get it in some other way. Luckily for us, the offer class has a state property for us to check whether the offer is "Active". First, let's start by making the a separate function to get the partner's name and avatar. function getPartnerDetails(offer: TradeOfferManager.TradeOffer, callback: (err: any, details: any): void): any { // check state of the offer if (offer.state == TradeOfferManager.ETradeOfferState.Active) { offer.getUserDetails(function(err, me, them) { if (err) { callback(err, {}); // idk if tsc will complain if theres no second param } else { callback(null, them); } }); } else { this.bot.community.getSteamUser(offer.partner, (err, user) => { if (err) { callback(err, {}); } else { callback(null, { personaName: user.name, avatarFull: user.getAvatarURL('full') }); } }); } } If the offer is active we obtain the details through the get user details method. Otherwise, we utilise getSteamUser from steamcommunity which returns a CSteamUser object which we can extract the personaName and avatarFull from and send it back. Now when we want to use it we simply do: let personaName; let avatarFull; getPartnerDetails(offer, function(err, details) { if (err) { log.debug('Error getting partner details, returning ? avatar and unknown name'); personaName = 'unknown'; avatarFull = 'https://p7.hiclipart.com/preview/313/980/1020/question-mark-icon-question-mark-png.jpg'; } else { log.debug('Successfully obtained partner details'); personaName = details.personaName; avatarFull = details.avatarFull; } const stringified = JSON.stringify(discordTradeSummary) .replace(/%partnerId%/g, partnerSteamID) .replace(/%partnerName%/g, partnerName) .replace(/%partnerAvatar%/g, partnerAvatar) .replace(/%offerId%/g, offer.id) .replace(/%tradeNum%/g, tradesMade.toString()) .replace(/%tradeSummary%/g, tradeSummary.replace('Offered:', '\\n Offered:')) .replace(/%currentTime%/g, moment().format('MMMM Do YYYY, HH:mm:ss') + ' UTC'); const jsonObject = JSON.parse(stringified); request.send(JSON.stringify(jsonObject)); }); and so now the sendWebHookTradeSummary looks like private sendWebHookTradeSummary(offer: TradeOfferManager.TradeOffer): void { const request = new XMLHttpRequest(); request.open('POST', process.env.DISCORD_WEBHOOK_TRADE_SUMMARY_URL); request.setRequestHeader('Content-type', 'application/json'); const partnerSteamID = offer.partner.toString(); const tradeSummary = offer.summarize(this.bot.schema); let tradesTotal = 0; const offerData = this.bot.manager.pollData.offerData; for (const offerID in offerData) { if (!Object.prototype.hasOwnProperty.call(offerData, offerID)) { continue; } if (offerData[offerID].handledByUs === true && offerData[offerID].isAccepted === true) { // Sucessful trades handled by the bot tradesTotal++; } } const tradesMade = process.env.TRADES_MADE_STARTER_VALUE ? +process.env.TRADES_MADE_STARTER_VALUE + tradesTotal : 0 + tradesTotal; let personaName; let avatarFull; getPartnerDetails(offer, function(err, details) { if (err) { log.debug('Error getting partner details, returning ? avatar and unknown name'); personaName = 'unknown'; avatarFull = 'https://p7.hiclipart.com/preview/313/980/1020/question-mark-icon-question-mark-png.jpg'; } else { log.debug('Successfully obtained partner details'); personaName = details.personaName; avatarFull = details.avatarFull; } const stringified = JSON.stringify(discordTradeSummary) .replace(/%partnerId%/g, partnerSteamID) .replace(/%partnerName%/g, partnerName) .replace(/%partnerAvatar%/g, partnerAvatar) .replace(/%offerId%/g, offer.id) .replace(/%tradeNum%/g, tradesMade.toString()) .replace(/%tradeSummary%/g, tradeSummary.replace('Offered:', '\\n Offered:')) .replace(/%currentTime%/g, moment().format('MMMM Do YYYY, HH:mm:ss') + ' UTC'); const jsonObject = JSON.parse(stringified); request.send(JSON.stringify(jsonObject)); }); log.debug('Accepted trade summmary sent to webhook'); } Lemme know if there are any errors, I haven't tested this. Edited April 16, 2020 by vrtgn Quote
IdiNium Posted April 16, 2020 Author Report Posted April 16, 2020 (edited) 2 hours ago, vrtgn said: Happy to help, but note that a function is assignable to a variable; you just can't return that value from the function. You will progress as you learn. I'll be using callbacks below so here is a tutorial/explanation if you still don't entirely understand. Thank you for the link on the tutorial/explanation about it. It's a good video than the others that I've watched. It's a good speed for a beginner like me to catch up with it. Well, back to the coding, TypeScript detects some problem from the function that you've shared to me and this is what I came back nicer: function getPartnerDetails(offer: TradeOfferManager.TradeOffer, callback: (err: any, details: any) => void): any { // I need to put arrow function there // check state of the offer if (offer.state === TradeOfferManager.ETradeOfferState.active) { offer.getUserDetails(function(err, me, them) { // It seems you missed this part. if (err) { callback(err, {}); } else { callback(null, them); } }); } else { this.bot.community.getSteamUser(offer.partner, (err, user) => { if (err) { callback(err, {}); } else { callback(null, { personName: user.name, avatarFull: user.getAvatarURL('full') }); } }); } } However, I am not really sure where I should put this function into. Currently, I put these lines of code between the import modules and the main body of the MyHandler.ts file. I've also tried do as you do for the sendWebhookTradeSummary, and it's all good there. However, there's an error at "this" on the function getPartnerDetails as shown below: And I am not so sure how to solve this issue. I've tried as shown below and import SteamCommunity from 'steamcommunity': Move hovering on the highlighted getSteamUser show it does not exist: Then I take a look into the ./src/types/modules/ and found a folder named steamcommunity, and when I open the index.d.ts file there, I can not found the getSteamUser function. Here is the content of the index.d.ts for steamcommunity module (this is the one that the developer made it himself right?): declare module 'steamcommunity' { import { EventEmitter } from 'events'; import SteamID from 'steamid'; import { CookieJar } from 'request'; interface Events { sessionExpired: () => void; confKeyNeeded: (tag: string, callback: (err?: Error, time?: number, confKey?: string) => void) => void; } class SteamCommunity extends EventEmitter { constructor(options?: object); steamID: SteamID | null; _jar: CookieJar; loggedIn(callback: Function): void; getSessionID(): string; getWebAPIKey(domain: string, callback: (err?: Error, key?: string) => void); setCookies(cookies: string[]): void; editProfile( settings: { name?: string; realName?: string; summary?: string; country?: string; state?: string; city?: string; customURL?: string; featuredBadge?: number; primaryGroup?: SteamID | string; }, callback?: (err?: Error) => void ): void; profileSettings( settings: { profile?: number; comments?: number; inventory?: number; inventoryGifts?: boolean; gameDetails?: number; playTime?: boolean; friendsList?: number; }, callback?: (err?: Error) => void ): void; uploadAvatar( image: Buffer | string /* , format?: string */, callback?: (err?: Error, url?: string) => void ): void; inviteUserToGroup(userID: SteamID | string, groupID: SteamID | string, callback?: (err?: Error) => void): void; getSteamGroup(id: SteamID | string, callback: (err?: Error, group?: SteamCommunity.Group) => void): void; getTradeURL(callback: (err?: Error, url?: string, token?: string) => void): void; acceptConfirmationForObject(identitySecret: string, objectID: string, callback: (err?: Error) => void): void; } namespace SteamCommunity { interface Group { steamID: SteamID; name: string; url: string; headline: string; summary: string; avatarHash: Buffer; members: number; membersInChat: number; membersInGame: number; membersOnline: number; join: (callback?: (err?: Error) => void) => void; } } export = SteamCommunity; } So I got confused on how to go for another steps. Btw I've pushed these changes to my git repo for tracking. (https://github.com/idinium96/tf2-automatic/tree/partner-avatar-and-name) Again, I am sorry for your time and effort and thank you so much. Edited April 16, 2020 by IdiNium Quote
vrtgn Posted April 16, 2020 Report Posted April 16, 2020 You can make the function a private and place it in the body of the MyHandler, but you will have to call it by prefixing "this." before the function. this.getPartnerDetails(...) As for the "this" error you can change the parameters of the getPartnerDetails to: function getPartnerDetails(this: any, offer: TradeOfferManager.TradeOffer, callback: (err: any, details: any) => void): any { Dr. McKay 1 Quote
IdiNium Posted April 16, 2020 Author Report Posted April 16, 2020 I've now put the function in the body of MyHandler and set it to private and have added "this" to the getPartnerDetails inside the sendWebHookReviewOfferSummary and sendWebHookTradeSummary, but then TypeScript still detect some error when I add back "this.bot" to the community.getSteamUser function. I think I get this error because as I mentioned before, the steamcommunity module is modified and declared at ./src/types/modules/steamcommunity that does not contained getSteamUser inside that, so, I then try to import CSteamUser from '../../node_modules/steamcommunity' which in my understanding, it will get the CSteamUser directly from node_modules instead of the module that have been made in ./src/types/modules/steamcommunity. Then I tried this and got no problem, so I compile it and try run the bot and make a trade and accept it: And the error as shown in the terminal. When I mouse over the sComm.community.getSteamUser(...) they all typed as any. Now I really not sure what to do. Quote
vrtgn Posted April 17, 2020 Report Posted April 17, 2020 (edited) The CSteamUser is a class, but as the docs say: Quote This class cannot be instantiated directly; it must be received from a call to getSteamUser. Instantiated in other words means created like you have done: const sComm = new CSteamUser(); The only way to get it is by using the getSteamUser call. On 4/17/2020 at 12:54 AM, IdiNium said: I think I get this error because as I mentioned before, the steamcommunity module is modified and declared at ./src/types/modules/steamcommunity that does not contained getSteamUser inside that. Actually you can assign methods to the steamcommunity class using prototype based inheritance. Essentially we can refer to the steamcommunity class from another file and give it a method that we want and if you look at the CSteamUser.js file you can see that steamcommunity is bought in and assigned the getSteamUser method: var SteamCommunity = require('../index.js'); ... SteamCommunity.prototype.getSteamUser = function(id, callback) {...} BUT, you say the file is modified (and yes it was made by Nick) instead it seems like Quote *.d.ts files are used to provide typescript type information about a module that's written in JavaScript, for example underscore / lodash / aws-sdk (Source) So the file is a sort of skeleton/description about the steamcommunity module. I'm assuming the methods defined in that file are only methods the bot needs to use. So if that is the case then we can add the getSteamUser method in that file: getSteamUser(id: SteamID | string, callback: (err?: Error, user?: any) => void): void; For simplicity and to be on the safe side Im going to keep user as any but you could make it a type of SteamCommunity.User but you will have to add the SteamCommunity.User interface in the SteamCommunity namespace: interface User { name: string; getAvatarURL: (size: string) => void; } I rarely use TS so this maybe wrong but let me know how it goes. Edited April 24, 2020 by vrtgn Fixed getAvatarURL typo Quote
IdiNium Posted April 18, 2020 Author Report Posted April 18, 2020 Hello @vrtgn thanks again for your help. 7 hours ago, vrtgn said: Instantiated in other words means created like you have done: I get it now, really don't know that before. ⁝ } ⁝ getSteamUser(id: SteamID | string, callback: (err?: Error, user?: any) => void): void; ⁝ } ⁝ namespace SteamCommunity { interface Group { ⁝ } interface User { name: string; getAvatarUrl: (size: string) => void; } } To be honest, I have the same idea before of adding this to the steamcommunity index.d.ts, but I don't really sure at that time that I had to cancel doing it, and also at that time, I don't know about the "getAvatarUrl: (size: string) => void; thing. When I do apply this to the code, it does not throw any error, also I've changed back from what I did (the sComm thing) to the (this.bot.community) and it also does not detect any error and the compilation is done perfectly. I also had try to run the bot, and I seems to no have crashed as before, but it does not wait until it get the personaName and avatarFull does make the bot send to discord the wrong format of JSON and discord rejected it. Here's the logging: And this is the current coding for sendWebHookTradeSummary: private sendWebHookTradeSummary(offer: TradeOfferManager.TradeOffer): void { const request = new XMLHttpRequest(); request.open('POST', process.env.DISCORD_WEBHOOK_TRADE_SUMMARY_URL); request.setRequestHeader('Content-type', 'application/json'); const partnerSteamID = offer.partner.toString(); const tradeSummary = offer.summarize(this.bot.schema); let tradesTotal = 0; const offerData = this.bot.manager.pollData.offerData; for (const offerID in offerData) { if (!Object.prototype.hasOwnProperty.call(offerData, offerID)) { continue; } if (offerData[offerID].handledByUs === true && offerData[offerID].isAccepted === true) { // Sucessful trades handled by the bot tradesTotal++; } } const tradesMade = process.env.TRADES_MADE_STARTER_VALUE ? +process.env.TRADES_MADE_STARTER_VALUE + tradesTotal : 0 + tradesTotal; let personaName; let avatarFull; log.debug('getting partner Avatar and Name...'); // It does read here but then it does not wait until it get the personaName or avatarFull this.getPartnerDetails(offer, function(err, details) { if (err) { log.debug('Error retrieving partner Avatar and Name: ', err); personaName = 'unknown'; avatarFull = 'https://p7.hiclipart.com/preview/313/980/1020/question-mark-icon-question-mark-png.jpg'; } else { log.debug('partner Avatar and Name retrieved. Applying...'); // It read here after sent to webhook personaName = details.personaName; avatarFull = details.personaName; } const stringified = JSON.stringify(discordTradeSummary) .replace(/%partnerId%/g, partnerSteamID) .replace(/%partnerName%/g, personaName) .replace(/%partnerAvatar%/g, avatarFull) .replace(/%offerId%/g, offer.id) .replace(/%tradeNum%/g, tradesMade.toString()) .replace(/%tradeSummary%/g, tradeSummary.replace('Offered:', '\\n Offered:')) .replace(/%currentTime%/g, moment().format('MMMM Do YYYY, HH:mm:ss') + ' UTC'); const jsonObject = JSON.parse(stringified); request.send(JSON.stringify(jsonObject)); // Got sent before getting avatarFull and personaName }); log.debug('Accepted trade summmary sent to webhook'); } Does the callback function is not working? Quote
IdiNium Posted April 18, 2020 Author Report Posted April 18, 2020 Oh wait, I watched back the video you linked about callback. So, it's the asynchronize that makes it run the other line of codes while getting the information to be loaded and then execute them when it retrieve it right? How should I do to make it to wait until it gets the avatarFull and personaName? Quote
IdiNium Posted April 18, 2020 Author Report Posted April 18, 2020 Update: It seems that I've put the log.debug things at the wrong place, that's why it print out that it has sent to webhook first. I've update the coding and here's the whole coding for function sendWebHookReviewOfferSummary (which use the getUserDetails with no problem at all), sendWebHookTradeSummary (which you'll find out at the log that I'll show after the coding) and the getPartnerDetails (which is yours): private sendWebHookReviewOfferSummary(offer: TradeOfferManager.TradeOffer, reason: string): void { const request = new XMLHttpRequest(); request.open('POST', process.env.DISCORD_WEBHOOK_REVIEW_OFFER_URL); request.setRequestHeader('Content-type', 'application/json'); const partnerSteamID = offer.partner.toString(); const tradeSummary = offer.summarize(this.bot.schema); let partnerAvatar; let partnerName; log.debug('getting partner Avatar and Name...'); offer.getUserDetails(function(err, me, them) { if (err) { log.debug('Error retrieving partner Avatar and Name: ', err); partnerAvatar = 'https://p7.hiclipart.com/preview/313/980/1020/question-mark-icon-question-mark-png.jpg'; partnerName = 'unknown'; return; } log.debug('partner Avatar and Name retrieved. Applying...'); partnerAvatar = them.avatarFull; log.debug(partnerAvatar); partnerName = them.personaName; log.debug(partnerName); const stringified = JSON.stringify(discordReviewOfferSummary) .replace(/%partnerId%/g, partnerSteamID) .replace(/%partnerName%/g, partnerName) .replace(/%partnerAvatar%/g, partnerAvatar) .replace(/%offerId%/g, offer.id) .replace(/%reason%/g, reason) .replace(/%tradeSummary%/g, tradeSummary.replace('Offered:', '\\n Offered:')) .replace(/%ownerDiscordId%/g, process.env.OWNER_DISCORD_ID) .replace(/%currentTime%/g, moment().format('MMMM Do YYYY, HH:mm:ss') + ' UTC'); const jsonObject = JSON.parse(stringified); request.send(JSON.stringify(jsonObject)); log.debug('Review offer summary sent to webhook'); }); } private sendWebHookTradeSummary(offer: TradeOfferManager.TradeOffer): void { const request = new XMLHttpRequest(); request.open('POST', process.env.DISCORD_WEBHOOK_TRADE_SUMMARY_URL); request.setRequestHeader('Content-type', 'application/json'); const partnerSteamID = offer.partner.toString(); const tradeSummary = offer.summarize(this.bot.schema); let tradesTotal = 0; const offerData = this.bot.manager.pollData.offerData; for (const offerID in offerData) { if (!Object.prototype.hasOwnProperty.call(offerData, offerID)) { continue; } if (offerData[offerID].handledByUs === true && offerData[offerID].isAccepted === true) { // Sucessful trades handled by the bot tradesTotal++; } } const tradesMade = process.env.TRADES_MADE_STARTER_VALUE ? +process.env.TRADES_MADE_STARTER_VALUE + tradesTotal : 0 + tradesTotal; let personaName; let avatarFull; log.debug('getting partner Avatar and Name...'); this.getPartnerDetails(offer, function(err, details) { if (err) { log.debug('Error retrieving partner Avatar and Name: ', err); personaName = 'unknown'; avatarFull = 'https://p7.hiclipart.com/preview/313/980/1020/question-mark-icon-question-mark-png.jpg'; return; } log.debug('partner Avatar and Name retrieved. Applying...'); personaName = details.personaName; log.debug(personaName); avatarFull = details.personaName; log.debug(avatarFull); const stringified = JSON.stringify(discordTradeSummary) .replace(/%partnerId%/g, partnerSteamID) .replace(/%partnerName%/g, personaName) .replace(/%partnerAvatar%/g, avatarFull) .replace(/%offerId%/g, offer.id) .replace(/%tradeNum%/g, tradesMade.toString()) .replace(/%tradeSummary%/g, tradeSummary.replace('Offered:', '\\n Offered:')) .replace(/%currentTime%/g, moment().format('MMMM Do YYYY, HH:mm:ss') + ' UTC'); const jsonObject = JSON.parse(stringified); request.send(JSON.stringify(jsonObject)); log.debug('Accepted trade summmary sent to webhook'); }); } private getPartnerDetails(offer: TradeOfferManager.TradeOffer, callback: (err: any, details: any) => void): any { // check state of the offer if (offer.state === TradeOfferManager.ETradeOfferState.active) { offer.getUserDetails(function(err, me, them) { if (err) { callback(err, {}); } else { callback(null, them); } }); } else { this.bot.community.getSteamUser(offer.partner, (err, user) => { if (err) { callback(err, {}); } else { callback(null, { personName: user.name, avatarFull: user.getAvatarURL('full') }); } }); } } And here's the log: //sendWebHookReviewOfferSummary part↓ 2020-04-18 11:04:34 debug: getting partner Avatar and Name... 2020-04-18 11:04:34 debug: Processing next offer 2020-04-18 11:04:34 debug: Already processing offer or queue is empty 2020-04-18 11:04:35 debug: partner Avatar and Name retrieved. Applying... 2020-04-18 11:04:35 debug: https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/02/02d8e088a531ac2603cde55c826cf06a1e335f9c_full.jpg (✔) 2020-04-18 11:04:35 debug: IdiNium-Fumino (✔) 2020-04-18 11:04:35 debug: Review offer summary sent to webhook //sendWebHookReviewOfferSummary part↑ - Received both avatar (in url) and name perfectly. 2020-04-18 11:05:05 info: Message from IdiNium | Quicksell.store (76561198013127982): !accepttrade 4000953535 2020-04-18 11:05:05 info: Message sent to IdiNium | Quicksell.store (76561198013127982): Accepting offer... 2020-04-18 11:05:08 trade: Offer #4000953535 from 76561198077208792 successfully accepted; confirmation required 2020-04-18 11:05:08 debug: Accepting mobile confirmation... {"offerId":"4000953535"} 2020-04-18 11:05:08 debug: Offer #4000953535 from 76561198077208792 done doing action on offer 2020-04-18 11:05:12 debug: Checking account info 2020-04-18 11:05:13 verbose: Offer #4000953535 from 76561198077208792 state changed: Active -> Accepted (reason: MANUAL) 2020-04-18 11:05:13 debug: Took unknown ms to process offer {"offerId":"4000953535","state":3,"finishTime":null} 2020-04-18 11:05:15 info: Message sent to 76561198077208792: ✅Success! The offer went through successfully. Feel free to join our Discord server for support and full tips to setup tf2-automatic and tf2-autocord using VPS and other stuffs: https://discord.gg/AXTGF4g 2020-04-18 11:05:15 trade: Offer #4000953535 from 76561198077208792 has been accepted. //sendWebHookTradeSummary part↓ 2020-04-18 11:05:15 debug: getting partner Avatar and Name... 2020-04-18 11:05:15 debug: Enqueueing smelt job for 5002 2020-04-18 11:05:15 debug: Can't handle job {"job":{"type":"smelt","defindex":5002}} 2020-04-18 11:05:15 debug: Ensuring TF2 GC connection... 2020-04-18 11:05:15 debug: Not playing TF2 2020-04-18 11:05:15 debug: Enqueueing smelt job for 5002 2020-04-18 11:05:15 debug: Already handling queue 2020-04-18 11:05:15 debug: Enqueueing sort job 2020-04-18 11:05:15 debug: Already handling queue 2020-04-18 11:05:15 debug: partner Avatar and Name retrieved. Applying... 2020-04-18 11:05:15 debug: undefined (❌) 2020-04-18 11:05:15 debug: undefined (❌) 2020-04-18 11:05:15 debug: Accepted trade summmary sent to webhook //sendWebHookTradeSummary part↑ - Does not received both avatar (in url) and name, does it actually failed to send to webhook. Both were returned undefined. Should that actually an error? Quote
vrtgn Posted April 18, 2020 Report Posted April 18, 2020 (edited) @IdiNium Oh damn, I never realised that there are two separate functions for review offer and trade summary function but now I come to think of it it makes sense why. Do you have any idea how I can enable discord webhooks to my account to test this feature and give you a solid answer rather than just guessing? btw ts makes me cry lol @IdiNium Hey, just looked over your code and saw that in the getPartnerDetails function in getSteamUser callback you return the object with the property personName rather than personaName. In addition, in the sendSummary you assign partnerAvatar to details.personaName rather than details.avatarFull. You may also want to if else in the summary as you are assigning the values of partnerName and partnerAvatar but returning before you send them. Edited April 18, 2020 by vrtgn Quote
IdiNium Posted April 18, 2020 Author Report Posted April 18, 2020 14 hours ago, vrtgn said: Oh damn, I never realised that there are two separate functions for review offer and trade summary function but now I come to think of it it makes sense why. @vrtgn Sorry for the long late reply, yes there are two separate function that I am working on. The review offer is working just fine since the offer is still active, but not for when the trade is accepted. 14 hours ago, vrtgn said: Do you have any idea how I can enable discord webhooks to my account to test this feature and give you a solid answer rather than just guessing? Sure, but I am not sure if here is the correct place to do this, but there you go. You can git clone the repo that I am working on, do navigate to where you want the tf2-automatic folder to be created first and run in terminal or cmd (I assume you have installed git and nodejs on your system): git clone https://github.com/idinium96/tf2-automatic.git && cd tf2-automatic && git checkout partner-avatar-and-name && npm install If you haven't yet install TypeScript, then install it globally: npm install typescript@latest -g and then compile the code (navigated to tf2-automatic folder): npm run build then you need to edit the template.env file and fill in the required variables. Note that you need to use Steam Desktop Authenticator (SDA) to extract the shared_secret and identity_secret of your alt steam account. It can be found inside maFiles folder inside SDA main folder. For getting the webhook URL, first make sure you have created your own Discord server and create a channel. Click on the channel and click Edit Channel (⚙), and click on Webhooks. Click Create Webhook and copy the Webhook URL there and paste it the env. You can have separate webhook for accepted trade summary and for review offer (create another one and copy and paste the link). For your discord ID, make sure you've enabled Developer mode in your Discord settings, then right click on yourself and click copy ID and paste it there. This is just to make the webhook ping (mentioned) you when there's an offer to be reviewed. After you've completed filled in the required variables, save the template.env as only .env (only the extension without any name). Then to run the bot, run node dist/app.js Then to test to see if the sendWebHookReviewOfferSummary and sendWebHookTradeSummary is working or not, first make sure you send some items to the bot (the bot will accept any trade from ADMINS) so that an exchange of item can be done. Then, since you're still not added any item to the bot's price list, then any offer that you send using another alt account (or with the help of your friend) to the bot will triggered an offer to be reviewed. This will be sent to the webhook you've set. Then, to see if sendWebHookTradeSummary (that is currently not working), copy the trade number or and offerId (in bracket) and send "!accepttrade <offerId>" to the bot via steam chat. Then if you see the log it will show the value for both avatarFull and personaName are undefined. 14 hours ago, vrtgn said: Hey, just looked over your code and saw that in the getPartnerDetails function in getSteamUser callback you return the object with the property personName rather than personaName. In addition, in the sendSummary you assign partnerAvatar to details.personaName rather than details.avatarFull. Oh ya, I have not realised that. Thank you for noticing it. 14 hours ago, vrtgn said: You may also want to if else in the summary as you are assigning the values of partnerName and partnerAvatar but returning before you send them. I am not sure by what you mean by this. Btw, thank you very much and feel free to ask if you got any problem running the bot. You may also read more about it here (but the original one does not include the Discord Webhook Integration): https://github.com/Nicklason/tf2-automatic Quote
IdiNium Posted April 20, 2020 Author Report Posted April 20, 2020 Hey @vrtgn! I think I've solve this. Since getAvatarUrl is not working properly (maybe due to my mistake or idk why), so I decided to get avatarHash instead, and it turned out to be working fine after about 20 times of accepted trades. Here's my coding: Myhandler.ts (only the part that I'm working on): private sendWebHookReviewOfferSummary(offer: TradeOfferManager.TradeOffer, reason: string): void { const request = new XMLHttpRequest(); request.open('POST', process.env.DISCORD_WEBHOOK_REVIEW_OFFER_URL); request.setRequestHeader('Content-type', 'application/json'); const partnerSteamID = offer.partner.toString(); const tradeSummary = offer.summarize(this.bot.schema); let partnerAvatar; let partnerName; log.debug('getting partner Avatar and Name...'); offer.getUserDetails(function(err, me, them) { if (err) { log.debug('Error retrieving partner Avatar and Name: ', err); partnerAvatar = 'https://p7.hiclipart.com/preview/313/980/1020/question-mark-icon-question-mark-png.jpg'; partnerName = 'unknown'; } else { log.debug('partner Avatar and Name retrieved. Applying...'); partnerAvatar = them.avatarFull; log.debug(partnerAvatar); partnerName = them.personaName; log.debug(partnerName); } const stringified = JSON.stringify(discordReviewOfferSummary) .replace(/%partnerId%/g, partnerSteamID) .replace(/%partnerName%/g, partnerName) .replace(/%partnerAvatar%/g, partnerAvatar) .replace(/%offerId%/g, offer.id) .replace(/%reason%/g, reason) .replace(/%tradeSummary%/g, tradeSummary.replace('Offered:', '\\n Offered:')) .replace(/%ownerDiscordId%/g, process.env.OWNER_DISCORD_ID) .replace(/%currentTime%/g, moment().format('MMMM Do YYYY, HH:mm:ss') + ' UTC'); const jsonObject = JSON.parse(stringified); request.send(JSON.stringify(jsonObject)); log.debug('Review offer summary sent to webhook'); }); } private sendWebHookTradeSummary(offer: TradeOfferManager.TradeOffer): void { const request = new XMLHttpRequest(); request.open('POST', process.env.DISCORD_WEBHOOK_TRADE_SUMMARY_URL); request.setRequestHeader('Content-type', 'application/json'); const partnerSteamID = offer.partner.toString(); const tradeSummary = offer.summarize(this.bot.schema); let tradesTotal = 0; const offerData = this.bot.manager.pollData.offerData; for (const offerID in offerData) { if (!Object.prototype.hasOwnProperty.call(offerData, offerID)) { continue; } if (offerData[offerID].handledByUs === true && offerData[offerID].isAccepted === true) { // Sucessful trades handled by the bot tradesTotal++; } } const tradesMade = process.env.TRADES_MADE_STARTER_VALUE ? +process.env.TRADES_MADE_STARTER_VALUE + tradesTotal : 0 + tradesTotal; let personaName; let avatarFull; let avatarFullPrint; log.debug('getting partner Avatar and Name...'); this.getPartnerDetails(offer, function(err, details) { if (err) { log.debug('Error retrieving partner Avatar and Name: ', err); personaName = 'unknown'; avatarFullPrint = 'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/72/72f78b4c8cc1f62323f8a33f6d53e27db57c2252_full.jpg'; //default "?" image } else { log.debug('partner Avatar and Name retrieved. Applying...'); personaName = details.personaName; log.debug(personaName); avatarFull = details.avatarFull ? details.avatarFull : '72f78b4c8cc1f62323f8a33f6d53e27db57c2252'; //if something wrong, it'll use the default "?"" image log.debug(avatarFull); avatarFullPrint = 'https://steamcdn-a.akamaihd.net/steamcommunity/public/images/avatars/' + avatarFull.substring(0, 2) + '/' + avatarFull + '_full.jpg'; log.debug(avatarFullPrint); } const stringified = JSON.stringify(discordTradeSummary) .replace(/%partnerId%/g, partnerSteamID) .replace(/%partnerName%/g, personaName) .replace(/%partnerAvatar%/g, avatarFullPrint) .replace(/%offerId%/g, offer.id) .replace(/%tradeNum%/g, tradesMade.toString()) .replace(/%tradeSummary%/g, tradeSummary.replace('Offered:', '\\n Offered:')) .replace(/%currentTime%/g, moment().format('MMMM Do YYYY, HH:mm:ss') + ' UTC'); const jsonObject = JSON.parse(stringified); request.send(JSON.stringify(jsonObject)); log.debug('Accepted trade summmary sent to webhook'); }); } private getPartnerDetails(offer: TradeOfferManager.TradeOffer, callback: (err: any, details: any) => void): any { // check state of the offer if (offer.state === TradeOfferManager.ETradeOfferState.active) { offer.getUserDetails(function(err, me, them) { if (err) { callback(err, {}); } else { callback(null, them); } }); } else { this.bot.community.getSteamUser(offer.partner, (err, user) => { if (err) { callback(err, {}); } else { callback(null, { personaName: user.name, avatarFull: user.avatarHash }); } }); } } And here's the steamcommunity module (index.d.ts): ⁝ getSteamUser(id: SteamID | string, callback: (err?: Error, user?: SteamCommunity.User) => void): void; acceptConfirmationForObject(identitySecret: string, objectID: string, callback: (err?: Error) => void): void; } namespace SteamCommunity { interface Group { steamID: SteamID; name: string; url: string; headline: string; summary: string; avatarHash: Buffer; members: number; membersInChat: number; membersInGame: number; membersOnline: number; join: (callback?: (err?: Error) => void) => void; } interface User { steamID: SteamID; name: string; onlineState: string; stateMessage: string; privacyState: string; visibilityState: string; avatarHash: string; vacBanned: string; tradeBanState: string; isLimitedAccount: string; customURL: string; groups: null; primaryGroup: null; } } export = SteamCommunity; } I think I'll stick on this solution for now. If you have any other alternative or maybe make it look nicer, I'd like to know it! Thank you very much vrtgn 1 Quote
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.