├── LICENSE ├── README.md ├── discordstorage ├── .DS_Store ├── Session.py └── core.py ├── ds.py └── requirements.txt /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Nigel 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Discord Storage 2 | Utilize Discord servers as cloud storage! [This only works on python 3-3.6.x] 3 | 4 | ## Tutorial 5 | #### Setting up the bot/server 6 | 7 | ##### 1) Creating the bot 8 | In order for this program to work, you're going to need to create a discord bot so we can connect to the discord API. Go to [this](https://discordapp.com/developers/applications/me) link to create a bot. Make sure to create a user bot and ensure the bot is private. [Here's](http://i.imgur.com/QIWBksk.png) a picture to the configuration. **Keep note of the token and the client ID.** 9 | ##### 2) Setting up the server 10 | The bot will need a place to upload files. Create a new discord server, make sure no one else is on it unless you want them to access your files. 11 | 12 | ##### 3) Adding your bot to the server 13 | To add the bot to the server (assuming your bot isn't public), go to the following link: https://discordapp.com/oauth2/authorize?client_id={CLIENT_ID}&scope=bot&permissions=0 14 | Replace {CLIENT_ID} with the client ID you copied earlier. Then, select the server you just made and authorize. Your server should now show your bot like [this](http://i.imgur.com/NnqQAv7.png). 15 | 16 | #### Setting up the program 17 | ##### 1) Dependecies 18 | Clone the repository and run ```pip install -r requirements.txt``` to install the dependencies for the program. 19 | ##### 2) Configuration 20 | Run ```python ds.py``` to begin configuration of the bot. When prompted, copy and paste your **token** from when you created your bot. For the channel ID, copy the channel ID with right click on the channel (developer mode must be enabled under appearance on Discord settings to have the option for Copy ID). Your configuration should look like [this](http://i.imgur.com/g72BDoG.png) 21 | 22 | *You can delete ```config.discord``` to reconfigure the program.* 23 | #### Commands 24 | Usage: ```python ds.py [flag] {args}``` 25 | 26 | ```-upload /full_path/file.exe``` The -upload or -u flag and the full file path uploads a file. 27 | 28 | ```-download {FILE_CODE}``` The -download or -d flag and the file code will download a file from the discord server. Refer to the ```-list``` command to see uploaded file codes. 29 | 30 | ```-list``` The -list or -l flag will list all the file names/codes/sizes uploaded to the discord server. 31 | 32 | ```-help``` The -help or -h flag will display the help message (these commands listed here). 33 | 34 | 35 | #### Disclaimer 36 | You shouldn't be using this as your main source of file storage. Program was inspired by [snapchat-fs](https://github.com/hausdorff/snapchat-fs). 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /discordstorage/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nigel/DiscordStorage/cc0a19286257ce3cdf12f4a10331372ebf7b6a38/discordstorage/.DS_Store -------------------------------------------------------------------------------- /discordstorage/Session.py: -------------------------------------------------------------------------------- 1 | import discord,asyncio 2 | 3 | ''' 4 | this class uses discord.py 5 | seperate API will be written soon 6 | to reduce # of dependencies 7 | Refer to the following documentation: 8 | http://discordpy.readthedocs.io/en/latest/api.html 9 | ''' 10 | 11 | global client,loop,channelid 12 | intents = discord.Intents.default() 13 | client = discord.Client(intents=intents) #discord client object 14 | loop = None #async loop. used by other classes to add coroutines 15 | channelid = None 16 | 17 | class Session: 18 | 19 | global client,loop,channelid 20 | 21 | def __init__(self,token,channel): 22 | global channelid 23 | self.token = token #bot token 24 | channelid = channel #channel ID the bot uploads files to 25 | #closes all connections 26 | #RUNS ON MAIN THREAD, ASYNC. 27 | async def logout(self): 28 | await client.close() 29 | #initializes the loop once connected to 30 | #discord servers. 31 | #RUNS ON MAIN THREAD, ASYNC. 32 | @client.event 33 | async def on_ready(): 34 | global client,loop,channelid 35 | loop = asyncio.get_event_loop() 36 | if client.get_channel(int(channelid)) == None: 37 | print("Channel ID doesn't exist, reconfigure the program or update config.discord") 38 | 39 | #Returns text channel bot is uploading files to 40 | def getChannel(self): 41 | return client.get_channel(int(channelid)) 42 | 43 | #Connects to discord servers. 44 | #LEADS TO ASYNC LOOP. RUNS ON MAIN THREAD. 45 | def start(self): 46 | client.run(self.token) 47 | 48 | #Returns the client object 49 | def getClient(self): 50 | return client 51 | 52 | #Returns the async loop. 53 | def getLoop(self): 54 | return loop 55 | -------------------------------------------------------------------------------- /discordstorage/core.py: -------------------------------------------------------------------------------- 1 | import os,io,aiohttp,asyncio, discord 2 | from .Session import Session 3 | 4 | class Core: 5 | 6 | def __init__(self,directory,token,channel): 7 | self.directory = directory #set root directory for downloaded/files to be uploaded 8 | self.session = Session(token,channel) #discord API 9 | self.client = self.session.getClient() #discord API client object 10 | 11 | #check if the client is connected to discord servers 12 | def isready(self): 13 | return not(self.session.getLoop() == None) 14 | 15 | #starts conenction to discord servers. 16 | #RUNS ON MAIN THREAD, ASYNC. 17 | def start(self): 18 | self.session.start() 19 | 20 | #Halts all connection to discord servers. 21 | def logout(self): 22 | future = asyncio.run_coroutine_threadsafe(self.session.logout(), self.session.getLoop()) 23 | 24 | #runs the async_upload in a threadsafe way, 25 | #can be run from anything outisde of main thread. 26 | def upload(self,inp,code): 27 | future = asyncio.run_coroutine_threadsafe(self.async_upload(inp,code), self.session.getLoop()) 28 | try: 29 | return future.result() 30 | except Exception as exc: 31 | print(exc) 32 | return -1 33 | 34 | #runs the async_download in a threadsafe way, 35 | #can be run from an ything outside of main thread. 36 | def download(self,inp): 37 | future = asyncio.run_coroutine_threadsafe(self.async_download(inp), self.session.getLoop()) 38 | try: 39 | return future.result() 40 | except Exception as exc: 41 | print('[ERROR] ' + exc) 42 | return -1 43 | 44 | #Downloads a file from the server. 45 | #The list object in this format is needed: [filename,size,[DL URLs]] 46 | #RUNS ON MAIN THREAD, ASYNC. 47 | async def async_download(self,inp): 48 | os.makedirs(os.path.dirname(self.directory + "downloads/" + inp[0]), exist_ok=True) 49 | f = open(self.directory + "downloads/" + inp[0],'wb') 50 | for i in range(0,len(inp[2])): 51 | #user agent is not in compliance with Discord API rules. Change accordingly if needed 52 | agent = {'User-Agent':'DiscordStorageBot (http://github.com/nigel/discordstorage)'} 53 | async with aiohttp.ClientSession() as session: 54 | async with session.get(inp[2][i],headers=agent) as r: 55 | if r.status == 200: 56 | async for data in r.content.iter_any(): 57 | f.write(data) 58 | f.close() 59 | #files[code] = [name,size,[urls]] 60 | 61 | #Uploads a file to the server from the root directory, or any other directory specified 62 | #inp = directory, code = application-generated file code 63 | #RUNS ON MAIN THREAD, ASYNC. 64 | async def async_upload(self,inp,code): 65 | urls = [] 66 | f = open(inp,'rb') 67 | for i in range(0,self.splitFile(inp)): 68 | o = io.BytesIO(f.read(24000000)) 69 | discord_file = discord.File(fp=o,filename=code+"." + str(i)) 70 | await self.session.getChannel().send(file=discord_file) 71 | async for message in self.session.getChannel().history(limit=None): 72 | if message.author == self.client.user: 73 | urls.append(message.attachments[0].url) 74 | break 75 | f.close() 76 | 77 | return [os.path.basename(inp),os.path.getsize(inp),urls] 78 | 79 | #Finds out how many file blocks are needed to upload a file. 80 | #Regular max upload size at a time: 8MB. 81 | #Discord NITRO max upload size at a time: 50MB. 82 | #Change accordingly if needed. 83 | def splitFile(self,f): 84 | if (os.path.getsize(f)/24000000) > 1: 85 | return int(os.path.getsize(f)/24000000) + 1 86 | else: 87 | return 1 88 | -------------------------------------------------------------------------------- /ds.py: -------------------------------------------------------------------------------- 1 | from discordstorage import core 2 | import threading,json,asyncio,random,sys,argparse,os,time 3 | 4 | TOKEN_SECRET = "" #bot's secret token 5 | ROOM_ID = "" #channel text ID 6 | BOT_INFO = None 7 | FILES = None 8 | 9 | #Generates a file code from 0-4097 10 | def genCode(): 11 | code = str(random.randint(0,4098)) 12 | if FILES == None: 13 | return code 14 | while code in FILES.keys(): 15 | code = str(random.randint(0,4098)) 16 | return code 17 | 18 | #returns if the config file is configured or not. 19 | def isConfigured(): 20 | return os.path.isfile('config.discord') 21 | 22 | #invokes file uploading, to be used on a thread that's not in main thread. 23 | #writes to config file accordingly. 24 | def tellupload(line1,line2,cmd,code,client): 25 | while not (client.isready()): 26 | time.sleep(0.5) 27 | if not os.path.isfile(cmd): 28 | print('[ERROR] File does not exist.') 29 | client.logout() 30 | return 31 | flcode = client.upload(cmd,code) 32 | if flcode == -1: 33 | print('[ERROR] File upload fail') 34 | else: 35 | jobject = json.loads(line2) 36 | jobject[code] = flcode 37 | f = open('config.discord','w') 38 | f.write(line1) 39 | f.write(json.dumps(jobject)) 40 | f.close() 41 | print('[DONE] File upload complete') 42 | client.logout() 43 | 44 | def GetHumanReadable(size,precision=2): 45 | suffixes=['B','KB','MB','GB','TB'] 46 | suffixIndex = 0 47 | while size > 1024 and suffixIndex < 4: 48 | suffixIndex += 1 #increment the index of the suffix 49 | size = size/1024.0 #apply the division 50 | return "%.*f%s"%(precision,size,suffixes[suffixIndex]) 51 | 52 | #invokes file downloading, to be used on a thread that's not in main thread 53 | def telldownload(client,inp): 54 | while not (client.isready()): 55 | time.sleep(0.5) 56 | client.download(inp) 57 | client.logout() 58 | 59 | #parses cmd line arguments 60 | def parseArgs(inp): 61 | commands = ['-h','-help','-l','-list','-d','-download','-u','-upload'] 62 | if(len(inp) == 1): 63 | print('----------------------\n|DiscordStorage v0.1 |') 64 | print('|github.com/nigelchen|\n----------------------') 65 | print('\nUsage: python ds.py [command] (target)\n') 66 | print('COMMANDS:') 67 | print('[-h, -help] :: Show the current message') 68 | print('[-l, -list] :: Lists all the file informations that has been uploaded to the server.') 69 | print('[-d, -download] (FILE CODE) :: Downloads a file from the server. A filecode is taken in as the file identifier.') 70 | print('[-u, -upload] (FILE DIRECTORY) :: Uploads a file to the server. The full file directory is taken in for the argument.\n') 71 | elif isConfigured(): 72 | f = open('config.discord','r') 73 | first = f.readline() 74 | second = f.readline() 75 | f.close() 76 | TOKEN_SECRET = json.loads(first.replace("\\n",""))['TOKEN'] 77 | for el in inp: 78 | if '-d' == el or '-download' == el: 79 | if not ((not(FILES == None)) and (inp[inp.index(el)+1] in FILES.keys())): 80 | print('\n[ERROR] File code not found\n') 81 | else: 82 | obj = json.loads(second)[inp[inp.index(el)+1]] 83 | print('DOWNLOADING: ' + obj[0] ) 84 | print('SIZE: ' + GetHumanReadable(obj[1])) 85 | client = core.Core(os.getcwd() + "/",TOKEN_SECRET,ROOM_ID) 86 | threading.Thread(target=telldownload,args=(client,obj,)).start() 87 | client.start() 88 | break 89 | elif '-u' == el or '-upload' == el: 90 | print('UPLOADING: ' + inp[inp.index(el)+1]) 91 | client = core.Core(os.getcwd() + "/",TOKEN_SECRET,ROOM_ID) 92 | threading.Thread(target=tellupload,args=(first,second,inp[inp.index(el)+1],genCode(),client,)).start() 93 | client.start() 94 | break 95 | elif '-list' == el or '-l' == el: 96 | if not (FILES == None): 97 | print('\nFILES UPLOADED TO DISCORD:\n') 98 | for key in FILES.keys(): 99 | if FILES[key] == None: 100 | #correct nullfied attribute 101 | print(' [CONSOLE] Removed incorrect file with filecode ' + str(key)) 102 | f = open('config.discord','w') 103 | f.write(first) 104 | jobject = json.loads(second) 105 | del jobject[key] 106 | f.write(json.dumps(jobject)) 107 | f.close() 108 | else: 109 | print('name: ' + str(FILES[key][0]) + ' | code: ' + str(key) + ' | size: ' + GetHumanReadable(FILES[key][1]) +'') 110 | print('\n') 111 | break 112 | elif '-help' == el or '-h' == el: 113 | print('----------------------\n|DiscordStorage|') 114 | print('|github.com/nigel|\n----------------------') 115 | print('\nUsage: python ds.py [command] (target)\n') 116 | print('COMMANDS:') 117 | print('[-h, -help] :: Show the current message') 118 | print('[-l, -list] :: Lists all the file informations that has been uploaded to the server.') 119 | print('[-d, -download] (FILE CODE) :: Downloads a file from the server. A filecode is taken in as the file identifier.') 120 | print('[-u, -upload] (FILE DIRECTORY) :: Uploads a file to the server. The full file directory is taken in for the argument.\n') 121 | 122 | if not isConfigured(): 123 | print('Welcome to DiscordStorage.') 124 | print('Go to http://github.com/nigelchen/DiscordStorage for instructions.') 125 | TOKEN_SECRET = input('Bot token ID (Will be stored in plaintext in config file):') 126 | ROOM_ID = input ('Enter channel ID to store files in:') 127 | if len(ROOM_ID) <=0: 128 | ROOM_ID = None 129 | f = open('config.discord','w') 130 | f.write(str(json.dumps({'TOKEN':TOKEN_SECRET,'ROOM_ID':ROOM_ID})) + "\n") 131 | f.write(str(json.dumps({}))) 132 | f.close() 133 | else: 134 | f = open('config.discord','r') 135 | first = f.readline() 136 | second = f.readline() 137 | BOT_INFO = json.loads(first) 138 | FILES = json.loads(second) 139 | TOKEN_SECRET = json.loads(first.replace("\\n",""))['TOKEN'] 140 | ROOM_ID = json.loads(first.replace("\\n",""))['ROOM_ID'] 141 | f.close() 142 | 143 | try: 144 | parseArgs(sys.argv) 145 | except IndexError: 146 | print('\nUsage: python ds.py [command] (target)\n') 147 | print('COMMANDS:') 148 | print('[-h, -help] :: Show the help message') 149 | print('[-l, -list] :: Lists all the file informations that has been uploaded to the server.') 150 | print('[-d, -download] (FILE CODE) :: Downloads a file from the server. A filecode is taken in as the file identifier.') 151 | print('[-u, -upload] (FILE DIRECTORY) :: Uploads a file to the server. The full file directory is taken in for the argument.\n') 152 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | discord.py --------------------------------------------------------------------------------