├── src ├── __pycache__ │ └── config.cpython-310.pyc ├── config.py ├── update.py └── bot.py ├── LICENSE └── README.md /src/__pycache__/config.cpython-310.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TrippingLettuce/Shopify-Drop-Ship-Discord-Bot/main/src/__pycache__/config.cpython-310.pyc -------------------------------------------------------------------------------- /src/config.py: -------------------------------------------------------------------------------- 1 | # Discord 2 | 3 | bot_key = 'Bot Key' 4 | 5 | 6 | # Mongo 7 | 8 | mongo_key = 'Mongo Key' 9 | 10 | database = 'ShopifyBot' #Can be anything really -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Caleb Klinger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/update.py: -------------------------------------------------------------------------------- 1 | from urllib.request import urlopen 2 | import json 3 | from pymongo import MongoClient 4 | import time 5 | import urllib.parse 6 | 7 | 8 | client = MongoClient("Mongo Key") 9 | db = client["ShopifyBot"] 10 | r = 0 11 | #Inf loop with a sleep on it 12 | def getJSON(url): 13 | try: 14 | response = urlopen(url,timeout=20) 15 | except urllib.error.HTTPError as e: 16 | print("ERROR") 17 | print(e) 18 | data_json = json.loads(response.read()) 19 | return data_json 20 | 21 | 22 | while True: 23 | r += 1 24 | print(f"\nRun #{r}\n") 25 | #Get all collections in mongo 26 | collection_names = db.list_collection_names() 27 | 28 | #Loop though collections 29 | for name in collection_names: 30 | #Set collections and URL for comparisions 31 | collection = db[f"{name}"] 32 | #documents Get 33 | document_count = collection.count_documents({}) 34 | document_count = document_count - 1 #(dont count _id:0) 35 | 36 | counter = 1 37 | for x in range(document_count): 38 | prodData = collection.find_one({"_id": counter}) 39 | title = prodData["title"] 40 | price = prodData["price"] 41 | handle_id = prodData["handle"] 42 | handle_id = urllib.parse.quote(handle_id) 43 | last_buy = prodData["bought"] 44 | url = f"https://{name}/products/{handle_id}/products.json" 45 | data_json = getJSON(url) 46 | last_sold = data_json["product"]['updated_at'] 47 | counter += 1 48 | if last_buy != last_sold: #Item Bought 49 | counter -= 1 50 | print(f"{title} has been bought!") 51 | #New Buy Time, New Total Sales/Count Update 52 | prodData["bought"] = last_sold 53 | prodData["total_price"] += price 54 | prodData["total_sales"] += 1 55 | collection.replace_one({"_id":counter}, prodData) 56 | #New Total Sales(Week,Alltime) Update 57 | salesData = collection.find_one({"_id": 0}) 58 | salesData["total"] += price 59 | salesData["week"] += price 60 | collection.replace_one({"_id":0}, salesData) 61 | time.sleep(30) 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Discord Shopify Tracking Bot 2 | *Tracking Example:*
3 | Disisiss
4 | Welcome to the Discord Shopify Tracking Bot repository!
5 | This bot allows you to track sales and data for your Shopify products and stores, and receive regular updates on their performance.
6 | 7 | ### Bot 8 | To use the bot, add the following to config.py Mongo Token, Discord Bot Token, Mongo DB name 9 | 0.5 Commands
10 |  /track_product - Track a product(s) sales. Requires a product link.
11 |  /check_product_data - Check total sales and data for a specific product. Requires a product link.
12 | 13 | Note: If you're tracking a new store, you must first use /track_product.
14 | ![alt text](https://cdn.discordapp.com/attachments/1062641874852122735/1080413136441577493/ezgif.com-video-to-gif.gif) 15 | 16 | ### Tracker 17 | To use the tracker, add the following to Mongo Token, Mongo DB name 18 | This tracker will need to run 24/7 in the server. It runs in a while loop with a 30s sleep inbetween, lower sleep times may make mistakes.
19 | Run separately from bot
20 | 21 | 22 | ### Aditional Versions 23 | The 0.5 version has the basics and I will accept pull request if it can be made better, I have the finished 1.0 version and currently working on the 2.0 version 24 | I will publicly release the 1.0 when 2.0 is finished
25 | 26 | 27 | The full version 1.0 of the bot is available for purchase or customization. You can contact us on Discord or create an offer on Fiverr for more information.
28 | Here are some of the key features of version 1.0:
29 |  Track multiple products from the same store
30 |  Track stats of store-specific products with /check_store_data
31 |  More accurate tracking up to 99.9% accurate
32 |  Stop tracking after 30 days
33 |  Better UI with a gif command (/help)
34 | 35 | I am also working on version 2.0, which will include even more features such as:
36 |  User-specific products and stores
37 |  Button scroll display for products
38 |  Tracking entire stores
39 |  Custom pings
40 |  Tracking incoming products
41 |   Even better UI
42 | 43 | ### Other 44 | Discord - Tripping_Lettuce#3780
45 | Fiverr - https://www.fiverr.com/tripping_lettuc
46 | *I also do custom bot related to AI API and Character AI*
47 | 48 | 49 | -------------------------------------------------------------------------------- /src/bot.py: -------------------------------------------------------------------------------- 1 | 2 | import discord 3 | from discord.ext import commands 4 | from discord.ext.commands import Context 5 | from discord import app_commands 6 | from urllib.request import urlopen 7 | import json 8 | import pymongo 9 | from pymongo import MongoClient 10 | import datetime 11 | import config 12 | 13 | intents = discord.Intents.default() 14 | intents.members = True 15 | intents.message_content = True 16 | bot = commands.Bot(command_prefix='?', intents=intents) 17 | 18 | 19 | client = MongoClient(config.mongo_key) 20 | token = config.bot_key 21 | database = config.database 22 | db = client[database] 23 | 24 | 25 | 26 | #--- Bot Startup 27 | @bot.event 28 | async def on_ready(): 29 | print(f'Logged in as {bot.user}') 30 | 31 | 32 | #Help CMD 33 | @bot.tree.command() 34 | async def help(interaction: discord.Interaction): 35 | """Help - Bot Commands""" 36 | #Webhook 37 | data = { 38 | "title": "Bot Commands", 39 | "description": "Use **/help** to bring up this menu anytime.", 40 | "color": 15856113, 41 | "fields": [ 42 | { 43 | "name": "/track_product", 44 | "value": "Track a product(s) sales.\nRequires: `Product Link`" 45 | }, 46 | { 47 | "name": "/check_product_data", 48 | "value": "Check total sales & data for product.\nRequires: `Product Link`" 49 | }, 50 | { 51 | "name": "Product Link", 52 | "value": "EX: `https://juicleds.com/products/galaxy-projector`" 53 | }, 54 | { 55 | "name": "/check_store_data (Unavailable version)", 56 | "value": "Check total sales & data for store\nRequires: `Website Link`" 57 | }, 58 | ], 59 | "footer": { 60 | "text": "If you're tracking a new store, you must first use /track_product." 61 | }, 62 | } 63 | embed = discord.Embed.from_dict(data) 64 | await interaction.response.send_message(embed = embed) 65 | 66 | @bot.tree.command() 67 | async def upgrade(interaction: discord.Interaction): 68 | """More Commands""" 69 | #Webhook 70 | data = { 71 | "title": "Get the latest version", 72 | "description": "The full 1.0 version is avaliable to purchase or even custom modified\nAdd me on discord or create an offer on fiverr for more information", 73 | "color": 15856113, 74 | "fields": [ 75 | { 76 | "name": "Discord/Fiverr", 77 | "value": "`Tripping_Lettuce#3780`\n`https://www.fiverr.com/tripping_lettuc`" 78 | }, 79 | { 80 | "name": "Version 1.0!", 81 | "value": "Track multiple products from same store!\nTrack stats of store specific products with `/check_store_data`\n More accurate tracking up to `99.9%` accurate\nStop tracking after 30 days\nBetter UI with gif `\help`" 82 | }, 83 | { 84 | "name": "Version 2.0!!!", 85 | "value": "User Specfic Products and Stores\nButton scroll display for products\nTrack entire stores\nCustom Pings\nTrack incoming products\nEven better UI" 86 | }, 87 | 88 | ], 89 | "footer": { 90 | "text": "All profits go to paying of my student loans :)" 91 | }, 92 | "image": { 93 | "url": "https://cdn.discordapp.com/attachments/1062641874852122735/1080413136441577493/ezgif.com-video-to-gif.gif" 94 | } 95 | } 96 | embed = discord.Embed.from_dict(data) 97 | await interaction.response.send_message(embed = embed) 98 | 99 | 100 | @bot.tree.command() 101 | async def track_product(interaction: discord.Interaction,product_link:str): 102 | """Track a product(s) sales""" 103 | 104 | #Handel ID Get 105 | handle_id = product_link.split("/products/")[1] 106 | #Domain Get 107 | domain = product_link.split("/")[2] 108 | #Json Product Page Get 109 | url = f"https://{domain}/products/{handle_id}/products.json" 110 | #Open Json File Try 111 | try: 112 | response = urlopen(url) 113 | except: 114 | content = "There seems to be an error with the link use /help" 115 | await interaction.response.send_message(content=content, ephemeral=True) 116 | # Loads Json and Reads Try 117 | data_json = json.loads(response.read()) 118 | #Send to function to organize and send to mongo Func 119 | data = organize(data_json,domain,handle_id) 120 | embed = discord.Embed.from_dict(data) 121 | await interaction.response.send_message(embed = embed) 122 | 123 | 124 | 125 | 126 | def organize(data_json: str, domain:str,handle_id:str): 127 | 128 | 129 | # Go to Mongo Collection 130 | collection = db[domain] 131 | #Started Tracking Get 132 | today = datetime.datetime.today() 133 | formatted_date = today.strftime('%m-%d-%y') 134 | document_count = collection.count_documents({}) 135 | 136 | prod_handle = data_json["product"]['handle'] 137 | 138 | 139 | if document_count == 2: 140 | prodData = collection.find_one({"_id": 1}) 141 | db_handle = prodData["handle"] 142 | if db_handle == prod_handle: 143 | data = { 144 | "title": "This product is already being tracked", 145 | "description": "Use /check_product_data to check total sales & data for this product.\nRequires: `Product Link`", 146 | "color": 880808, 147 | } 148 | return data 149 | 150 | collection.insert_one({"_id":0,"track_start":formatted_date,"total":0.00,"week":0.00}) #time stamps to track time 151 | 152 | prod_name = data_json["product"]['title'] 153 | prod_bought = data_json["product"]['updated_at'] 154 | prod_post = data_json["product"]['published_at'] 155 | 156 | #Product Sales,Ammount Set 157 | sold_total = 0 158 | sold_prod = 0 159 | 160 | #Product Image Set 161 | src = data_json["product"]["images"][0]["src"] 162 | 163 | #Price from variants Get 164 | prod_price = data_json["product"]["variants"][0]["price"] 165 | prod_price = float(prod_price) #Convert to int 166 | 167 | #Put into mongo Push 168 | collection.insert_one({"_id":1,"title":prod_name,"handle":prod_handle,"bought":prod_bought,"post":prod_post,"image":src,"price":prod_price, "total_price":sold_total,"total_sales":sold_prod,"track_start":formatted_date}) 169 | 170 | url = "https://" + domain 171 | 172 | data = { 173 | "title": domain, 174 | "description": "Allow 24 hours for data to populate.", 175 | "url": url, 176 | "color": 15856113, 177 | "fields": [ 178 | { 179 | "name": "/track_product", 180 | "value": "Track a product sales.\nRequires: `Product Link`" 181 | }, 182 | { 183 | "name": "/check_product_data", 184 | "value": "Check total sales & data for product.\nRequires: `Product Link`" 185 | }, 186 | { 187 | "name": "/help", 188 | "value": "Show the list of commands." 189 | } 190 | ], 191 | "author": { 192 | "name": "Product Tracking Started" 193 | } 194 | } 195 | return data 196 | 197 | 198 | 199 | @bot.tree.command() 200 | async def check_product_data(interaction: discord.Interaction, product_link:str): 201 | """Check total sales & data for product""" 202 | 203 | #Handel ID Get 204 | handle_id = product_link.split("/products/")[1] 205 | #Domain Get 206 | domain = product_string = product_link.split("/")[2] 207 | #Get mongo colelction Get 208 | collection = db[domain] 209 | 210 | 211 | 212 | prodData = collection.find_one({"_id": 1}) 213 | 214 | 215 | #----Get Top Data----- 216 | title = prodData["title"] 217 | domain = domain.replace("/", "") 218 | handle = prodData["handle"] 219 | src = prodData["image"] 220 | prod_price = prodData["total_price"] 221 | prod_price = str(prod_price) 222 | prod_sales = prodData["total_sales"] 223 | track_start = prodData["track_start"] 224 | prod_pricessss = prodData["price"] 225 | #Get Post Date 226 | prod_post = prodData["post"] 227 | prod_post = prod_post.split("T")[0] 228 | 229 | prod_buy = prodData["bought"] 230 | prod_buy1 = prod_buy.split("T")[0] 231 | prod_buy2 = prod_buy.split("T")[1] 232 | prod_buy2 = prod_buy2.split("-")[0] 233 | prod_buy = f"{prod_buy1} : {prod_buy2}" 234 | 235 | product_link = "https://" + domain + '/products/' + handle 236 | 237 | 238 | data = { 239 | "title": title, 240 | "url": product_link, 241 | "color": 15856113, 242 | "fields": [ 243 | { 244 | "name": "Product Tracking Since", 245 | "value": track_start, 246 | "inline": True 247 | }, 248 | { 249 | "name": "Total Sales", 250 | "value": f"${prod_price}", 251 | "inline": True 252 | }, 253 | { 254 | "name": "7 Day Sales", 255 | "value": f"${prod_price}", 256 | "inline": True 257 | }, 258 | { 259 | "name": "Product Data", 260 | "value": f"-----------------------------\n**Name - {title}\nLast Sold - {prod_buy}\nCreated - {prod_post}\nPrice - {prod_pricessss}**\n-----------------------------" 261 | }, 262 | ], 263 | "author": { 264 | "name": "Product Sales Data" 265 | }, 266 | "footer": { 267 | "text": "Tracking stops if not searched in 30 days, or no sales in 3 days." 268 | }, 269 | "thumbnail": { 270 | "url": src 271 | } 272 | } 273 | 274 | embed = discord.Embed.from_dict(data) 275 | await interaction.response.send_message(embed = embed) 276 | 277 | 278 | #------ Sync Command ------ 279 | @bot.command() 280 | @commands.guild_only() 281 | @commands.is_owner() 282 | async def sync(ctx: Context): 283 | synced = await ctx.bot.tree.sync() 284 | await ctx.send(f"Synced {len(synced)} commands {'globally'}") 285 | 286 | 287 | bot.run(token) 288 | 289 | --------------------------------------------------------------------------------