discord.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. import axios from 'axios';
  2. import { logger } from './index.js';
  3. /**
  4. * Discord Webhook Service
  5. * Sends notifications for copy and close operations
  6. */
  7. export class DiscordWebhook {
  8. constructor(webhookUrl) {
  9. this.webhookUrl = webhookUrl;
  10. }
  11. /**
  12. * Send notification to Discord
  13. */
  14. async sendNotification(embed) {
  15. if (!this.webhookUrl) {
  16. logger.debug('Discord webhook URL not configured, skipping notification');
  17. return;
  18. }
  19. try {
  20. const response = await axios.post(
  21. this.webhookUrl,
  22. {
  23. embeds: [embed],
  24. username: 'Byreal Sniper Bot',
  25. avatar_url: 'https://i.imgur.com/4M34hi2.png', // Optional bot avatar
  26. },
  27. {
  28. timeout: 10000,
  29. headers: {
  30. 'Content-Type': 'application/json',
  31. },
  32. }
  33. );
  34. if (response.status === 204) {
  35. logger.success('Discord notification sent successfully');
  36. }
  37. } catch (error) {
  38. logger.error('Failed to send Discord notification:', error.message);
  39. if (error.response) {
  40. logger.error('Discord API response:', error.response.data);
  41. }
  42. }
  43. }
  44. /**
  45. * Send copy success notification
  46. */
  47. async notifyCopySuccess({
  48. parentPosition,
  49. poolAddress,
  50. poolName,
  51. tokenA,
  52. tokenB,
  53. copyUsdValue,
  54. txSignature,
  55. }) {
  56. const embed = {
  57. title: '✅ Position Copied Successfully',
  58. color: 0x00ff00, // Green
  59. timestamp: new Date().toISOString(),
  60. fields: [
  61. {
  62. name: '📋 Parent Position',
  63. value: `[${parentPosition.slice(0, 8)}...${parentPosition.slice(-8)}](https://solscan.io/account/${parentPosition})`,
  64. inline: true,
  65. },
  66. {
  67. name: '🏊 Pool',
  68. value: `${poolName}\n[${poolAddress.slice(0, 8)}...](https://solscan.io/account/${poolAddress})`,
  69. inline: true,
  70. },
  71. {
  72. name: '💰 Copy Amount',
  73. value: `$${copyUsdValue.toFixed(2)}`,
  74. inline: true,
  75. },
  76. {
  77. name: '💱 Token A',
  78. value: `${tokenA.amount} ${tokenA.symbol || ''}\n($${tokenA.valueUsd || 0})`,
  79. inline: true,
  80. },
  81. {
  82. name: '💱 Token B',
  83. value: `${tokenB.amount} ${tokenB.symbol || ''}\n($${tokenB.valueUsd || 0})`,
  84. inline: true,
  85. },
  86. ],
  87. footer: {
  88. text: 'Byreal Sniper Bot',
  89. },
  90. };
  91. if (txSignature) {
  92. embed.fields.push({
  93. name: '🔗 Transaction',
  94. value: `[View on Solscan](https://solscan.io/tx/${txSignature})`,
  95. inline: false,
  96. });
  97. }
  98. await this.sendNotification(embed);
  99. }
  100. /**
  101. * Send close success notification
  102. */
  103. async notifyCloseSuccess({
  104. positionAddress,
  105. poolName,
  106. closedAt,
  107. reason,
  108. txSignature,
  109. }) {
  110. const embed = {
  111. title: '🔴 Position Closed',
  112. color: 0xff0000, // Red
  113. timestamp: new Date().toISOString(),
  114. fields: [
  115. {
  116. name: '📋 Position',
  117. value: `[${positionAddress.slice(0, 8)}...${positionAddress.slice(-8)}](https://solscan.io/account/${positionAddress})`,
  118. inline: true,
  119. },
  120. {
  121. name: '🏊 Pool',
  122. value: poolName || 'Unknown',
  123. inline: true,
  124. },
  125. {
  126. name: '📝 Reason',
  127. value: reason || 'Manual close',
  128. inline: false,
  129. },
  130. ],
  131. footer: {
  132. text: 'Byreal Sniper Bot',
  133. },
  134. };
  135. if (txSignature) {
  136. embed.fields.push({
  137. name: '🔗 Transaction',
  138. value: `[View on Solscan](https://solscan.io/tx/${txSignature})`,
  139. inline: false,
  140. });
  141. }
  142. await this.sendNotification(embed);
  143. }
  144. /**
  145. * Send error notification
  146. */
  147. async notifyError({
  148. operation,
  149. positionAddress,
  150. error,
  151. }) {
  152. const embed = {
  153. title: '⚠️ Operation Failed',
  154. color: 0xffa500, // Orange
  155. timestamp: new Date().toISOString(),
  156. fields: [
  157. {
  158. name: '❌ Operation',
  159. value: operation,
  160. inline: true,
  161. },
  162. {
  163. name: '📋 Position',
  164. value: `[${positionAddress.slice(0, 8)}...${positionAddress.slice(-8)}](https://solscan.io/account/${positionAddress})`,
  165. inline: true,
  166. },
  167. {
  168. name: '🔴 Error',
  169. value: error.slice(0, 1000), // Discord field limit is 1024
  170. inline: false,
  171. },
  172. ],
  173. footer: {
  174. text: 'Byreal Sniper Bot',
  175. },
  176. };
  177. await this.sendNotification(embed);
  178. }
  179. }
  180. export default DiscordWebhook;