gofilepy.gofile

  1import requests
  2import os
  3from io import BufferedReader
  4from .exceptions import GofileAPIException
  5
  6
  7GofileFile = None
  8GofileFolder = None
  9GofileAccount = None
 10
 11class GofileClient (object):
 12    server = None
 13    BASE_DOMAIN = 'gofile.io'
 14    API_SUBDOMAIN = 'api'
 15    BASE_API_URL = 'https://'+API_SUBDOMAIN+'.'+BASE_DOMAIN
 16
 17    API_ROUTE_GET_SERVER_URL = BASE_API_URL + '/getServer'
 18    API_ROUTE_GET_ACCOUNT_URL = BASE_API_URL + "/getAccountDetails"
 19    API_ROUTE_GET_CONTENT_URL = BASE_API_URL + "/getContent"
 20
 21    API_ROUTE_DELETE_CONTENT_URL = BASE_API_URL + "/deleteContent"
 22    API_ROUTE_COPY_CONTENT_URL = BASE_API_URL + "/copyContent"
 23    API_ROUTE_CREATE_FOLDER_URL = BASE_API_URL + "/createFolder"
 24    API_ROUTE_SET_OPTION_URL = BASE_API_URL +  "/setOption"
 25
 26    API_ROUTE_DOWNLOAD_PATH = "download"
 27    API_ROUTE_UPLOAD_CONTENT_PATH = "uploadFile"
 28
 29    API_STORE_FORMAT = "https://{}.{}/{}"
 30
 31    def __init__(self, token: str = None, get_account: bool = True, verbose: bool = False):
 32        self.token = token
 33        self.server = GofileClient.get_best_server()
 34        self.verbose = verbose
 35
 36        if get_account and token:
 37            self.get_account()
 38
 39    @staticmethod
 40    def handle_response(resp: requests.Response):
 41        code = resp.status_code
 42        data = resp.json()
 43        got = None
 44        api_status = ''
 45
 46        if data:
 47            api_status = data.get('status')
 48            got = data.get('data')
 49
 50
 51        if api_status != 'ok' or code != 200:
 52            raise GofileAPIException.__init_from_resp__(resp)
 53        return got
 54
 55    def get_best_upload_url(self):
 56        return self.API_STORE_FORMAT.format(self.server, self.BASE_DOMAIN, self.API_ROUTE_UPLOAD_CONTENT_PATH)
 57
 58
 59    @staticmethod
 60    def get_best_server(throw_if_not_200=False):
 61        resp = requests.get(GofileClient.API_ROUTE_GET_SERVER_URL)
 62        return GofileClient.handle_response(resp)['server']
 63
 64
 65    def _download_file_from_direct_link(self, direct_link, out_dir="./"):
 66        fn = direct_link.rsplit('/', 1)[1]
 67        resp = requests.get(direct_link, stream=True, allow_redirects=None)
 68        if resp.status_code != 200:
 69            raise GofileAPIException("Could not download file", code=resp.status_code)
 70
 71        out_path = os.path.join(out_dir, fn)
 72        with open(out_path, 'wb') as f:
 73            for chunk in resp.iter_content(chunk_size=1024):
 74                if chunk:
 75                    f.write(chunk)
 76        return out_path
 77
 78
 79    def _get_token(self, token):
 80        if not token:
 81            token = self.token
 82            if not self.token:
 83                token = ""
 84        return token
 85
 86    def upload(self, path: str=None, file: BufferedReader=None, parent_id: str=None, token: str=None) -> GofileFile:
 87        if not file and not path:
 88            raise ValueError("GofileClient.upload() requires a BufferedReader or file path")
 89
 90        upload_url = self.get_best_upload_url()
 91        token = self._get_token(token)
 92
 93        data = {}
 94        if parent_id:
 95            data["folderId"] = parent_id
 96        if token:
 97            data["token"] = token
 98
 99        files = {}
100        if file:
101            files["file"] = file
102        elif path:
103            files["file"] = open(path, "rb")
104
105        resp = requests.post(upload_url, files=files, data=data)
106        got = GofileClient.handle_response(resp)
107
108        #Needed because json returned from API has different key values at this endpoint
109        got["id"] = got.get("fileId", None)
110        got["name"] = got.get("fileName", None)
111
112        return  GofileFile._load_from_dict(got, client=self)
113    
114
115    def _get_content_raw_resp(self, content_id: str, token: str = None):
116        token = self._get_token(token)
117        req_url = self.API_ROUTE_GET_CONTENT_URL + "?contentId="+content_id
118
119        if token:
120            req_url += "&token="+token
121
122        resp = requests.get(req_url)
123        data = GofileClient.handle_response(resp)
124        return resp, data
125
126
127    def get(self, content_id: str, token: str = None):
128        resp,data = self._get_content_raw_resp(content_id, token=token)
129        return GofileContent.__init_from_resp__(resp, client=self)
130
131    def get_folder(self, *args, **kwargs):
132        """Retrieves folder using content_id"""
133        return self.get(*args, **kwargs)
134
135    def delete(self, *content_ids: str, token: str = None):
136        """Calls Gofile API to delete provided content_ids."""
137        token = self._get_token(token)
138        data = {"contentsId": ",".join(content_ids), "token": token}
139        resp = requests.delete(GofileClient.API_ROUTE_DELETE_CONTENT_URL, data=data)
140        got = GofileClient.handle_response(resp)
141
142
143    def _get_account_raw_resp(self, token: str = None):
144        token = self._get_token(token)
145        url = GofileClient.API_ROUTE_GET_ACCOUNT_URL + "?token=" + token
146        resp = requests.get(url)
147        data = GofileClient.handle_response(resp)
148        return resp, data
149
150
151    def get_account(self, token: str = None) -> GofileAccount:
152        """If token is provided returns specified account, otherwise token defaults to self.token.
153          \If token is default self.account is updated"""
154        token = self._get_token(token)
155        resp, data = self._get_account_raw_resp(token=token)
156        account = GofileAccount._load_from_dict(data)
157        account._client = self
158        account._raw = data
159
160        if account.token == self.token:
161            self.account = account #update client's copy of account
162
163        return account
164
165    def set_content_option(self, content_id: str, option: str, value, token: str = None):
166        """Sets content option like 'description', 'public', etc (more at gofile.io/api).  Note that folder and file content have different options"""
167        token = self._get_token(token)
168
169        if type(value) == bool: #api expects bools to be string
170            if value:
171                value = "true"
172            else:
173                value = "false"
174
175        data = {
176            "contentId": content_id,
177            "token": token,
178            "option": option,
179            "value": value
180        }
181
182        resp = requests.put(GofileClient.API_ROUTE_SET_OPTION_URL, data=data)
183        got = GofileClient.handle_response(resp)
184
185
186    def copy_content(self, *content_ids: str, parent_id: str = None, token: str = None):
187        """Copy provided content_ids to destination folder's content_id.  Currently returns None because api doesn't return any information.  Will have to query parent folder"""
188        if not parent_id:
189            raise ValueError("Must pass a parent folder id: parent_id=None")
190
191        token = self._get_token(token)
192        data = {
193            "contentsId": ",".join(content_ids),
194            "folderIdDest": parent_id,
195            "token": token
196        }
197
198        resp = requests.put(GofileClient.API_ROUTE_COPY_CONTENT_URL, data=data)
199        got = GofileClient.handle_response(resp)
200        
201        """
202        As of right now /copyContent does not return information about newly created content
203        For this reason copy_content will just return None
204
205        return GofileContent.__init_from_resp__(resp, client=self)
206        """
207
208    def create_folder(self, name: str, parent_id: str, token: str = None):
209        """Creates folder in specified parent folder's content_id"""
210        token = self._get_token(token)
211
212        data = {
213            "parentFolderId": parent_id,
214            "folderName": name
215        }
216
217        data["token"] = token
218
219        resp = requests.put(GofileClient.API_ROUTE_CREATE_FOLDER_URL, data=data)
220        got = GofileClient.handle_response(resp)
221
222        return GofileContent.__init_from_resp__(resp, client=self) 
223
224class GofileAccount (object):
225    token: str
226    """Token of account, found on Gofile.io"""
227    email: str
228    """Email address of account"""
229    tier: str
230    """Tier of GofileAccount either Standard or Premium"""
231    root_id: str
232    """Root folder's content_id of Gofile account"""
233    folder_cnt: int
234    """Number of children folders"""
235    file_cnt: int
236    """Number of children files"""
237    total_size: int
238    """Total size of Accounts' contents"""
239    total_download_cnt: int
240    """Total download count of Accounts' contents"""
241
242    def __init__(self, token: str = None):
243        self.token = token
244        self.email = None
245        self.tier = None
246        self.root_id = None
247        self.folder_cnt = None
248        self.file_cnt = None
249        self.total_size = None
250        self.total_download_cnt = None
251        self._raw = {}
252
253    def _override_from_dict(self, data: dict) -> None:
254        self.token = data.get("token", self.token)
255        self.email = data.get("email", self.email)
256        self.tier = data.get("tier", self.tier)
257        self.root_id = data.get("rootFolder", self.root_id)
258        self.folder_cnt = data.get("foldersCount", self.folder_cnt)
259        self.file_cnt = data.get("filesCount", self.file_cnt)
260        self.total_size = data.get("totalSize", self.total_size)
261        self.total_download_cnt = data.get("totalDownloadCount", self.total_download_cnt)
262
263        self._raw = data
264
265    @staticmethod
266    def _load_from_dict(data: dict):
267        account = GofileAccount(data["token"])
268        account._override_from_dict(data)
269        return account
270    
271    def reload(self):
272        resp, data = self._client._get_account_raw_resp(token=self.token)
273        self._override_from_dict(data)
274        self._raw = data
275
276class GofileContent (object):
277    name: str
278    """Name of content"""
279    content_id: str
280    """Gofile API's unique content id"""
281    parent_id: str
282    """Content_id of parent folder"""
283    _type: str
284    """GofileContent subtypes, either 'file', 'folder' or 'unknown'."""
285    is_file_type: bool
286    """If GofileContent is a file"""
287    is_folder_type: bool
288    """If GofileContent is a folder"""
289    is_unknown_type: bool
290    """If GofileContent is unknown (call reload() to update)"""
291    is_deleted: bool
292    """If content is deleted (will only register if called by it's own method delete())"""
293
294    def __init__(self, content_id: str, parent_id: str, _type: str = None, client: GofileClient = None):
295        self.content_id = content_id
296        self.parent_id = parent_id
297        self._type = _type
298        self._client = client
299        self._raw = {}
300
301        self.is_file_type = _type == "file"
302        self.is_folder_type = _type == "folder"
303        self.is_unknown_type = _type == None
304        self.name = None
305        self.time_created = None
306        self.is_deleted = False
307
308    def __repr__ (self) -> str:
309        _type = self._type
310        if not _type:
311            _type = "Unknown"
312
313        return "<Gofile {}: content_id={} name={}>".format(_type.upper(), self.content_id, self.name)
314
315    def delete (self) -> None:
316        """Deletes itself.  When called successfully is_deleted = True"""
317        guest_token = self._raw.get("guestToken")
318        self._client.delete(self.content_id, token=guest_token)
319        self.is_deleted = True
320
321    def copy_to (self, dest_id: str) -> None:
322        """Copies itself to destination folder's content_id"""
323        self._client.copy_content(self.content_id, parent_id=dest_id)
324
325    def copy (self, dest_id: str) -> None:
326        self.copy_to(dest_id)
327
328    def set_option(self, option: str, value, reload: bool = True) -> None:
329        """Sets options for itself.  Full option list available at gofile.io/api"""
330        self._client.set_content_option(self.content_id, option, value)
331        if reload:
332            self.reload() #reload to get up to date information
333
334    def reload (self):
335        """Reloads any new updates to content.  If is_unknown_type = True then reload() returns updated content"""
336        if self.is_folder_type or (self.is_unknown_type and self.parent_id == None):
337            resp, data = self._client._get_content_raw_resp(self.content_id)
338
339            if self.is_unknown_type:
340                content = GofileContent.__init_from_resp__(resp, client=self._client)
341
342            elif self.is_folder_type:
343                self._override_from_dict(data)
344                return self
345        
346        elif (self.is_unknown_type or self.is_file_type) and self.parent_id:
347            resp, data = self._client._get_content_raw_resp(self.parent_id)
348            content_data = data["contents"].get(self.content_id, None)
349            content = None
350            if content_data:
351                content = GofileContent.__init_from_resp__({"data": content_data}, client=self._client)
352
353                if self.is_file_type:
354                    self._override_from_dict(content_data)
355
356            return content
357
358        else:
359            raise NotImplemented
360
361    @staticmethod
362    def __init_from_resp__ (resp: requests.Response, _type: str = None, client: GofileClient = None):
363        if type(resp) == requests.models.Response:
364            resp = resp.json()
365            
366        got = resp["data"] 
367        _type = got.get("type", _type)
368        content = None
369
370        if _type == "file":
371            content = GofileFile._load_from_dict(got, client=client)
372        elif _type == "folder":
373            content = GofileFolder._load_from_dict(got, client=client)
374        else:
375            raise NotImplemented
376
377        return content
378
379class GofileFile (GofileContent):
380    time_created: int
381    """Time that file was uploaded"""
382    size: int
383    """Size of file (in bytes)"""
384    download_cnt: int
385    """Amount of times the file has been downloaded"""
386    mimetype: str
387    """Mimetype of file"""
388    server: str
389    """subdomain server from where file will be downloaded (Premium)"""
390    direct_link: str
391    """Direct link to download file (Premium)"""
392    page_link: str
393    """Page link to view and get download url for file"""
394    md5: str
395    """Hash function of file for verification"""
396
397    def __init__(self, content_id: str, parent_id: str, client: GofileClient = None):
398        super().__init__(content_id, parent_id, _type="file", client=client)
399        self.name = None 
400        self.time_created = None
401        self.size = None
402        self.download_cnt = None
403        self.mimetype = None
404        self.server = None
405        self.direct_link = None
406        self.page_link = None
407        self.md5 = None
408
409    def _override_from_dict(self, data: dict) -> None:
410        self.content_id = data.get("id", self.content_id)
411        self.parent_id = data.get("parentFolder", self.parent_id)
412        self.name = data.get("name", self.name)
413        self.time_created = data.get("createTime", self.time_created)
414        self.size  = data.get("size", self.size)
415        self.download_cnt = data.get("downloadCount", self.download_cnt)
416        self.mimetype = data.get("mimetype", self.mimetype)
417        self.md5 = data.get("md5", self.md5)
418        self.server = data.get("serverChoosen", None)
419        link = data.get("link")
420        if link:
421            self.direct_link = self._get_direct_link_from_link(link)
422        self.page_link = data.get("downloadPage", self.page_link) 
423
424        self._raw = data
425
426    @staticmethod
427    def _get_direct_link_from_link (link):
428        idx = link.find("download/") + len("download/")-1
429        return link[:idx] + "/direct/" + link[idx+1:]
430
431    @staticmethod
432    def _load_from_dict(data: dict, client: GofileClient = None):
433        file = GofileFile(data["id"], data["parentFolder"], client=client)
434        file._override_from_dict(data)
435        file._raw = data
436        return file
437
438    def download(self, out_dir: str = "./") -> str:
439        """Downloads file to passed dir (default is working directory). Note: The option directLink
440          \needs to be True (Premium)"""
441
442        if self.direct_link:
443            return self._client._download_file_from_direct_link(self.direct_link, out_dir=out_dir)
444
445        else:
446            raise Exception("Direct link needed - set option directLink=True (only for premium users)") 
447
448class GofileFolder (GofileContent):
449    children: list[GofileContent]
450    """Children of folder, either GofileFolder or GofileFile type"""
451    children_ids: list[str]
452    """List of childrens' content ids"""
453    time_created: int
454    """Time folder was created"""
455    total_size: int
456    """Total size of folders' contents (in bytes)"""
457    is_public: bool
458    """Is folder publicly accessible"""
459    is_owner: bool
460    """If caller is folder owner"""
461    is_root: bool
462    """If folder is root folder"""
463    has_password: bool
464    """If folder is password protected"""
465    code: str
466    """Folder shortcode to access from the browser"""
467
468    def __init__(self, name: str, content_id: str, parent_id: str, client: GofileClient = None):
469        super().__init__(content_id, parent_id, _type="folder", client=client)
470        self.name = name
471        self.children = []
472        self.children_ids = []
473        self.total_size = None
474        self.total_download_cnt = None
475        self.time_created = None
476        self.is_public = None
477        self.is_owner = None
478        self.is_root = None
479        self.has_password = None
480        self.code = None
481
482    def __init_children_from_contents__(self, data: dict) -> None:
483        children = []
484
485        self.children_ids = data.get("childs")
486        contents = data.get("contents", {})
487
488        if contents.keys():
489            for content in contents.values():
490                child = GofileContent.__init_from_resp__({"data": content}, client=self._client)
491                children.append(child)
492
493        elif self.children_ids:
494            for child_id in self.children_ids:
495                child = GofileContent(child_id, parent_id=data["id"], client=self._client)
496                children.append(child)
497        return children
498
499
500    def _override_from_dict(self, data: dict) -> None:
501        self.content_id = data.get("id", self.content_id)
502        self.parent_id = data.get("parentFolder", self.parent_id)
503        self.name = data.get("name", self.name)
504        self.time_created = data.get("createTime", self.time_created)
505        self.is_public  = data.get("public", self.is_public)
506        self.is_owner = data.get("isOwner", self.is_owner)
507        self.is_root = data.get("isRoot", False)
508        self.has_password = data.get("password", self.has_password)
509        self.code = data.get("code", self.code)
510        self.total_size = data.get("totalSize", self.total_size)
511        self.total_download_cnt = data.get("totalDownloadCount", self.total_download_cnt)
512        self.children_ids = data.get("childs")
513
514        self.children = self.__init_children_from_contents__(data)
515
516        self._raw = data
517
518
519    @staticmethod
520    def _load_from_dict(data: dict, client: GofileClient = None) -> GofileFolder:
521        parent_id = data.get("parentFolder", None)
522        folder = GofileFolder(data["name"], data["id"], parent_id, client=client)
523        folder._override_from_dict(data)
524        folder._raw = data
525
526        return folder
527
528    def upload(self, path: str = None, file: BufferedReader = None) -> GofileFile:
529        return self._client.upload(file=file, path=path, parent_id=self.content_id)
class GofileFile(GofileContent):
380class GofileFile (GofileContent):
381    time_created: int
382    """Time that file was uploaded"""
383    size: int
384    """Size of file (in bytes)"""
385    download_cnt: int
386    """Amount of times the file has been downloaded"""
387    mimetype: str
388    """Mimetype of file"""
389    server: str
390    """subdomain server from where file will be downloaded (Premium)"""
391    direct_link: str
392    """Direct link to download file (Premium)"""
393    page_link: str
394    """Page link to view and get download url for file"""
395    md5: str
396    """Hash function of file for verification"""
397
398    def __init__(self, content_id: str, parent_id: str, client: GofileClient = None):
399        super().__init__(content_id, parent_id, _type="file", client=client)
400        self.name = None 
401        self.time_created = None
402        self.size = None
403        self.download_cnt = None
404        self.mimetype = None
405        self.server = None
406        self.direct_link = None
407        self.page_link = None
408        self.md5 = None
409
410    def _override_from_dict(self, data: dict) -> None:
411        self.content_id = data.get("id", self.content_id)
412        self.parent_id = data.get("parentFolder", self.parent_id)
413        self.name = data.get("name", self.name)
414        self.time_created = data.get("createTime", self.time_created)
415        self.size  = data.get("size", self.size)
416        self.download_cnt = data.get("downloadCount", self.download_cnt)
417        self.mimetype = data.get("mimetype", self.mimetype)
418        self.md5 = data.get("md5", self.md5)
419        self.server = data.get("serverChoosen", None)
420        link = data.get("link")
421        if link:
422            self.direct_link = self._get_direct_link_from_link(link)
423        self.page_link = data.get("downloadPage", self.page_link) 
424
425        self._raw = data
426
427    @staticmethod
428    def _get_direct_link_from_link (link):
429        idx = link.find("download/") + len("download/")-1
430        return link[:idx] + "/direct/" + link[idx+1:]
431
432    @staticmethod
433    def _load_from_dict(data: dict, client: GofileClient = None):
434        file = GofileFile(data["id"], data["parentFolder"], client=client)
435        file._override_from_dict(data)
436        file._raw = data
437        return file
438
439    def download(self, out_dir: str = "./") -> str:
440        """Downloads file to passed dir (default is working directory). Note: The option directLink
441          \needs to be True (Premium)"""
442
443        if self.direct_link:
444            return self._client._download_file_from_direct_link(self.direct_link, out_dir=out_dir)
445
446        else:
447            raise Exception("Direct link needed - set option directLink=True (only for premium users)") 
GofileFile( content_id: str, parent_id: str, client: GofileClient = None)
398    def __init__(self, content_id: str, parent_id: str, client: GofileClient = None):
399        super().__init__(content_id, parent_id, _type="file", client=client)
400        self.name = None 
401        self.time_created = None
402        self.size = None
403        self.download_cnt = None
404        self.mimetype = None
405        self.server = None
406        self.direct_link = None
407        self.page_link = None
408        self.md5 = None
time_created: int

Time that file was uploaded

size: int

Size of file (in bytes)

download_cnt: int

Amount of times the file has been downloaded

mimetype: str

Mimetype of file

server: str

subdomain server from where file will be downloaded (Premium)

md5: str

Hash function of file for verification

name

Name of content

def download(self, out_dir: str = './') -> str:
439    def download(self, out_dir: str = "./") -> str:
440        """Downloads file to passed dir (default is working directory). Note: The option directLink
441          \needs to be True (Premium)"""
442
443        if self.direct_link:
444            return self._client._download_file_from_direct_link(self.direct_link, out_dir=out_dir)
445
446        else:
447            raise Exception("Direct link needed - set option directLink=True (only for premium users)") 

Downloads file to passed dir (default is working directory). Note: The option directLink

eeds to be True (Premium)

class GofileFolder(GofileContent):
449class GofileFolder (GofileContent):
450    children: list[GofileContent]
451    """Children of folder, either GofileFolder or GofileFile type"""
452    children_ids: list[str]
453    """List of childrens' content ids"""
454    time_created: int
455    """Time folder was created"""
456    total_size: int
457    """Total size of folders' contents (in bytes)"""
458    is_public: bool
459    """Is folder publicly accessible"""
460    is_owner: bool
461    """If caller is folder owner"""
462    is_root: bool
463    """If folder is root folder"""
464    has_password: bool
465    """If folder is password protected"""
466    code: str
467    """Folder shortcode to access from the browser"""
468
469    def __init__(self, name: str, content_id: str, parent_id: str, client: GofileClient = None):
470        super().__init__(content_id, parent_id, _type="folder", client=client)
471        self.name = name
472        self.children = []
473        self.children_ids = []
474        self.total_size = None
475        self.total_download_cnt = None
476        self.time_created = None
477        self.is_public = None
478        self.is_owner = None
479        self.is_root = None
480        self.has_password = None
481        self.code = None
482
483    def __init_children_from_contents__(self, data: dict) -> None:
484        children = []
485
486        self.children_ids = data.get("childs")
487        contents = data.get("contents", {})
488
489        if contents.keys():
490            for content in contents.values():
491                child = GofileContent.__init_from_resp__({"data": content}, client=self._client)
492                children.append(child)
493
494        elif self.children_ids:
495            for child_id in self.children_ids:
496                child = GofileContent(child_id, parent_id=data["id"], client=self._client)
497                children.append(child)
498        return children
499
500
501    def _override_from_dict(self, data: dict) -> None:
502        self.content_id = data.get("id", self.content_id)
503        self.parent_id = data.get("parentFolder", self.parent_id)
504        self.name = data.get("name", self.name)
505        self.time_created = data.get("createTime", self.time_created)
506        self.is_public  = data.get("public", self.is_public)
507        self.is_owner = data.get("isOwner", self.is_owner)
508        self.is_root = data.get("isRoot", False)
509        self.has_password = data.get("password", self.has_password)
510        self.code = data.get("code", self.code)
511        self.total_size = data.get("totalSize", self.total_size)
512        self.total_download_cnt = data.get("totalDownloadCount", self.total_download_cnt)
513        self.children_ids = data.get("childs")
514
515        self.children = self.__init_children_from_contents__(data)
516
517        self._raw = data
518
519
520    @staticmethod
521    def _load_from_dict(data: dict, client: GofileClient = None) -> GofileFolder:
522        parent_id = data.get("parentFolder", None)
523        folder = GofileFolder(data["name"], data["id"], parent_id, client=client)
524        folder._override_from_dict(data)
525        folder._raw = data
526
527        return folder
528
529    def upload(self, path: str = None, file: BufferedReader = None) -> GofileFile:
530        return self._client.upload(file=file, path=path, parent_id=self.content_id)
GofileFolder( name: str, content_id: str, parent_id: str, client: GofileClient = None)
469    def __init__(self, name: str, content_id: str, parent_id: str, client: GofileClient = None):
470        super().__init__(content_id, parent_id, _type="folder", client=client)
471        self.name = name
472        self.children = []
473        self.children_ids = []
474        self.total_size = None
475        self.total_download_cnt = None
476        self.time_created = None
477        self.is_public = None
478        self.is_owner = None
479        self.is_root = None
480        self.has_password = None
481        self.code = None
children: list[GofileContent]

Children of folder, either GofileFolder or GofileFile type

children_ids: list[str]

List of childrens' content ids

time_created: int

Time folder was created

total_size: int

Total size of folders' contents (in bytes)

is_public: bool

Is folder publicly accessible

is_owner: bool

If caller is folder owner

is_root: bool

If folder is root folder

has_password: bool

If folder is password protected

code: str

Folder shortcode to access from the browser

name

Name of content

total_download_cnt
def upload( self, path: str = None, file: _io.BufferedReader = None) -> GofileFile:
529    def upload(self, path: str = None, file: BufferedReader = None) -> GofileFile:
530        return self._client.upload(file=file, path=path, parent_id=self.content_id)
class GofileAccount:
225class GofileAccount (object):
226    token: str
227    """Token of account, found on Gofile.io"""
228    email: str
229    """Email address of account"""
230    tier: str
231    """Tier of GofileAccount either Standard or Premium"""
232    root_id: str
233    """Root folder's content_id of Gofile account"""
234    folder_cnt: int
235    """Number of children folders"""
236    file_cnt: int
237    """Number of children files"""
238    total_size: int
239    """Total size of Accounts' contents"""
240    total_download_cnt: int
241    """Total download count of Accounts' contents"""
242
243    def __init__(self, token: str = None):
244        self.token = token
245        self.email = None
246        self.tier = None
247        self.root_id = None
248        self.folder_cnt = None
249        self.file_cnt = None
250        self.total_size = None
251        self.total_download_cnt = None
252        self._raw = {}
253
254    def _override_from_dict(self, data: dict) -> None:
255        self.token = data.get("token", self.token)
256        self.email = data.get("email", self.email)
257        self.tier = data.get("tier", self.tier)
258        self.root_id = data.get("rootFolder", self.root_id)
259        self.folder_cnt = data.get("foldersCount", self.folder_cnt)
260        self.file_cnt = data.get("filesCount", self.file_cnt)
261        self.total_size = data.get("totalSize", self.total_size)
262        self.total_download_cnt = data.get("totalDownloadCount", self.total_download_cnt)
263
264        self._raw = data
265
266    @staticmethod
267    def _load_from_dict(data: dict):
268        account = GofileAccount(data["token"])
269        account._override_from_dict(data)
270        return account
271    
272    def reload(self):
273        resp, data = self._client._get_account_raw_resp(token=self.token)
274        self._override_from_dict(data)
275        self._raw = data
GofileAccount(token: str = None)
243    def __init__(self, token: str = None):
244        self.token = token
245        self.email = None
246        self.tier = None
247        self.root_id = None
248        self.folder_cnt = None
249        self.file_cnt = None
250        self.total_size = None
251        self.total_download_cnt = None
252        self._raw = {}
token: str

Token of account, found on Gofile.io

email: str

Email address of account

tier: str

Tier of GofileAccount either Standard or Premium

root_id: str

Root folder's content_id of Gofile account

folder_cnt: int

Number of children folders

file_cnt: int

Number of children files

total_size: int

Total size of Accounts' contents

total_download_cnt: int

Total download count of Accounts' contents

def reload(self):
272    def reload(self):
273        resp, data = self._client._get_account_raw_resp(token=self.token)
274        self._override_from_dict(data)
275        self._raw = data
class GofileClient:
 12class GofileClient (object):
 13    server = None
 14    BASE_DOMAIN = 'gofile.io'
 15    API_SUBDOMAIN = 'api'
 16    BASE_API_URL = 'https://'+API_SUBDOMAIN+'.'+BASE_DOMAIN
 17
 18    API_ROUTE_GET_SERVER_URL = BASE_API_URL + '/getServer'
 19    API_ROUTE_GET_ACCOUNT_URL = BASE_API_URL + "/getAccountDetails"
 20    API_ROUTE_GET_CONTENT_URL = BASE_API_URL + "/getContent"
 21
 22    API_ROUTE_DELETE_CONTENT_URL = BASE_API_URL + "/deleteContent"
 23    API_ROUTE_COPY_CONTENT_URL = BASE_API_URL + "/copyContent"
 24    API_ROUTE_CREATE_FOLDER_URL = BASE_API_URL + "/createFolder"
 25    API_ROUTE_SET_OPTION_URL = BASE_API_URL +  "/setOption"
 26
 27    API_ROUTE_DOWNLOAD_PATH = "download"
 28    API_ROUTE_UPLOAD_CONTENT_PATH = "uploadFile"
 29
 30    API_STORE_FORMAT = "https://{}.{}/{}"
 31
 32    def __init__(self, token: str = None, get_account: bool = True, verbose: bool = False):
 33        self.token = token
 34        self.server = GofileClient.get_best_server()
 35        self.verbose = verbose
 36
 37        if get_account and token:
 38            self.get_account()
 39
 40    @staticmethod
 41    def handle_response(resp: requests.Response):
 42        code = resp.status_code
 43        data = resp.json()
 44        got = None
 45        api_status = ''
 46
 47        if data:
 48            api_status = data.get('status')
 49            got = data.get('data')
 50
 51
 52        if api_status != 'ok' or code != 200:
 53            raise GofileAPIException.__init_from_resp__(resp)
 54        return got
 55
 56    def get_best_upload_url(self):
 57        return self.API_STORE_FORMAT.format(self.server, self.BASE_DOMAIN, self.API_ROUTE_UPLOAD_CONTENT_PATH)
 58
 59
 60    @staticmethod
 61    def get_best_server(throw_if_not_200=False):
 62        resp = requests.get(GofileClient.API_ROUTE_GET_SERVER_URL)
 63        return GofileClient.handle_response(resp)['server']
 64
 65
 66    def _download_file_from_direct_link(self, direct_link, out_dir="./"):
 67        fn = direct_link.rsplit('/', 1)[1]
 68        resp = requests.get(direct_link, stream=True, allow_redirects=None)
 69        if resp.status_code != 200:
 70            raise GofileAPIException("Could not download file", code=resp.status_code)
 71
 72        out_path = os.path.join(out_dir, fn)
 73        with open(out_path, 'wb') as f:
 74            for chunk in resp.iter_content(chunk_size=1024):
 75                if chunk:
 76                    f.write(chunk)
 77        return out_path
 78
 79
 80    def _get_token(self, token):
 81        if not token:
 82            token = self.token
 83            if not self.token:
 84                token = ""
 85        return token
 86
 87    def upload(self, path: str=None, file: BufferedReader=None, parent_id: str=None, token: str=None) -> GofileFile:
 88        if not file and not path:
 89            raise ValueError("GofileClient.upload() requires a BufferedReader or file path")
 90
 91        upload_url = self.get_best_upload_url()
 92        token = self._get_token(token)
 93
 94        data = {}
 95        if parent_id:
 96            data["folderId"] = parent_id
 97        if token:
 98            data["token"] = token
 99
100        files = {}
101        if file:
102            files["file"] = file
103        elif path:
104            files["file"] = open(path, "rb")
105
106        resp = requests.post(upload_url, files=files, data=data)
107        got = GofileClient.handle_response(resp)
108
109        #Needed because json returned from API has different key values at this endpoint
110        got["id"] = got.get("fileId", None)
111        got["name"] = got.get("fileName", None)
112
113        return  GofileFile._load_from_dict(got, client=self)
114    
115
116    def _get_content_raw_resp(self, content_id: str, token: str = None):
117        token = self._get_token(token)
118        req_url = self.API_ROUTE_GET_CONTENT_URL + "?contentId="+content_id
119
120        if token:
121            req_url += "&token="+token
122
123        resp = requests.get(req_url)
124        data = GofileClient.handle_response(resp)
125        return resp, data
126
127
128    def get(self, content_id: str, token: str = None):
129        resp,data = self._get_content_raw_resp(content_id, token=token)
130        return GofileContent.__init_from_resp__(resp, client=self)
131
132    def get_folder(self, *args, **kwargs):
133        """Retrieves folder using content_id"""
134        return self.get(*args, **kwargs)
135
136    def delete(self, *content_ids: str, token: str = None):
137        """Calls Gofile API to delete provided content_ids."""
138        token = self._get_token(token)
139        data = {"contentsId": ",".join(content_ids), "token": token}
140        resp = requests.delete(GofileClient.API_ROUTE_DELETE_CONTENT_URL, data=data)
141        got = GofileClient.handle_response(resp)
142
143
144    def _get_account_raw_resp(self, token: str = None):
145        token = self._get_token(token)
146        url = GofileClient.API_ROUTE_GET_ACCOUNT_URL + "?token=" + token
147        resp = requests.get(url)
148        data = GofileClient.handle_response(resp)
149        return resp, data
150
151
152    def get_account(self, token: str = None) -> GofileAccount:
153        """If token is provided returns specified account, otherwise token defaults to self.token.
154          \If token is default self.account is updated"""
155        token = self._get_token(token)
156        resp, data = self._get_account_raw_resp(token=token)
157        account = GofileAccount._load_from_dict(data)
158        account._client = self
159        account._raw = data
160
161        if account.token == self.token:
162            self.account = account #update client's copy of account
163
164        return account
165
166    def set_content_option(self, content_id: str, option: str, value, token: str = None):
167        """Sets content option like 'description', 'public', etc (more at gofile.io/api).  Note that folder and file content have different options"""
168        token = self._get_token(token)
169
170        if type(value) == bool: #api expects bools to be string
171            if value:
172                value = "true"
173            else:
174                value = "false"
175
176        data = {
177            "contentId": content_id,
178            "token": token,
179            "option": option,
180            "value": value
181        }
182
183        resp = requests.put(GofileClient.API_ROUTE_SET_OPTION_URL, data=data)
184        got = GofileClient.handle_response(resp)
185
186
187    def copy_content(self, *content_ids: str, parent_id: str = None, token: str = None):
188        """Copy provided content_ids to destination folder's content_id.  Currently returns None because api doesn't return any information.  Will have to query parent folder"""
189        if not parent_id:
190            raise ValueError("Must pass a parent folder id: parent_id=None")
191
192        token = self._get_token(token)
193        data = {
194            "contentsId": ",".join(content_ids),
195            "folderIdDest": parent_id,
196            "token": token
197        }
198
199        resp = requests.put(GofileClient.API_ROUTE_COPY_CONTENT_URL, data=data)
200        got = GofileClient.handle_response(resp)
201        
202        """
203        As of right now /copyContent does not return information about newly created content
204        For this reason copy_content will just return None
205
206        return GofileContent.__init_from_resp__(resp, client=self)
207        """
208
209    def create_folder(self, name: str, parent_id: str, token: str = None):
210        """Creates folder in specified parent folder's content_id"""
211        token = self._get_token(token)
212
213        data = {
214            "parentFolderId": parent_id,
215            "folderName": name
216        }
217
218        data["token"] = token
219
220        resp = requests.put(GofileClient.API_ROUTE_CREATE_FOLDER_URL, data=data)
221        got = GofileClient.handle_response(resp)
222
223        return GofileContent.__init_from_resp__(resp, client=self) 
GofileClient(token: str = None, get_account: bool = True, verbose: bool = False)
32    def __init__(self, token: str = None, get_account: bool = True, verbose: bool = False):
33        self.token = token
34        self.server = GofileClient.get_best_server()
35        self.verbose = verbose
36
37        if get_account and token:
38            self.get_account()
server = None
BASE_DOMAIN = 'gofile.io'
API_SUBDOMAIN = 'api'
BASE_API_URL = 'https://api.gofile.io'
API_ROUTE_GET_SERVER_URL = 'https://apigofilepy.gofile.io/getServer'
API_ROUTE_GET_ACCOUNT_URL = 'https://apigofilepy.gofile.io/getAccountDetails'
API_ROUTE_GET_CONTENT_URL = 'https://apigofilepy.gofile.io/getContent'
API_ROUTE_DELETE_CONTENT_URL = 'https://apigofilepy.gofile.io/deleteContent'
API_ROUTE_COPY_CONTENT_URL = 'https://apigofilepy.gofile.io/copyContent'
API_ROUTE_CREATE_FOLDER_URL = 'https://apigofilepy.gofile.io/createFolder'
API_ROUTE_SET_OPTION_URL = 'https://apigofilepy.gofile.io/setOption'
API_ROUTE_DOWNLOAD_PATH = 'download'
API_ROUTE_UPLOAD_CONTENT_PATH = 'uploadFile'
API_STORE_FORMAT = 'https://{}.{}/{}'
token
verbose
@staticmethod
def handle_response(resp: requests.models.Response):
40    @staticmethod
41    def handle_response(resp: requests.Response):
42        code = resp.status_code
43        data = resp.json()
44        got = None
45        api_status = ''
46
47        if data:
48            api_status = data.get('status')
49            got = data.get('data')
50
51
52        if api_status != 'ok' or code != 200:
53            raise GofileAPIException.__init_from_resp__(resp)
54        return got
def get_best_upload_url(self):
56    def get_best_upload_url(self):
57        return self.API_STORE_FORMAT.format(self.server, self.BASE_DOMAIN, self.API_ROUTE_UPLOAD_CONTENT_PATH)
@staticmethod
def get_best_server(throw_if_not_200=False):
60    @staticmethod
61    def get_best_server(throw_if_not_200=False):
62        resp = requests.get(GofileClient.API_ROUTE_GET_SERVER_URL)
63        return GofileClient.handle_response(resp)['server']
def upload( self, path: str = None, file: _io.BufferedReader = None, parent_id: str = None, token: str = None) -> None:
 87    def upload(self, path: str=None, file: BufferedReader=None, parent_id: str=None, token: str=None) -> GofileFile:
 88        if not file and not path:
 89            raise ValueError("GofileClient.upload() requires a BufferedReader or file path")
 90
 91        upload_url = self.get_best_upload_url()
 92        token = self._get_token(token)
 93
 94        data = {}
 95        if parent_id:
 96            data["folderId"] = parent_id
 97        if token:
 98            data["token"] = token
 99
100        files = {}
101        if file:
102            files["file"] = file
103        elif path:
104            files["file"] = open(path, "rb")
105
106        resp = requests.post(upload_url, files=files, data=data)
107        got = GofileClient.handle_response(resp)
108
109        #Needed because json returned from API has different key values at this endpoint
110        got["id"] = got.get("fileId", None)
111        got["name"] = got.get("fileName", None)
112
113        return  GofileFile._load_from_dict(got, client=self)
def get(self, content_id: str, token: str = None):
128    def get(self, content_id: str, token: str = None):
129        resp,data = self._get_content_raw_resp(content_id, token=token)
130        return GofileContent.__init_from_resp__(resp, client=self)
def get_folder(self, *args, **kwargs):
132    def get_folder(self, *args, **kwargs):
133        """Retrieves folder using content_id"""
134        return self.get(*args, **kwargs)

Retrieves folder using content_id

def delete(self, *content_ids: str, token: str = None):
136    def delete(self, *content_ids: str, token: str = None):
137        """Calls Gofile API to delete provided content_ids."""
138        token = self._get_token(token)
139        data = {"contentsId": ",".join(content_ids), "token": token}
140        resp = requests.delete(GofileClient.API_ROUTE_DELETE_CONTENT_URL, data=data)
141        got = GofileClient.handle_response(resp)

Calls Gofile API to delete provided content_ids.

def get_account(self, token: str = None) -> None:
152    def get_account(self, token: str = None) -> GofileAccount:
153        """If token is provided returns specified account, otherwise token defaults to self.token.
154          \If token is default self.account is updated"""
155        token = self._get_token(token)
156        resp, data = self._get_account_raw_resp(token=token)
157        account = GofileAccount._load_from_dict(data)
158        account._client = self
159        account._raw = data
160
161        if account.token == self.token:
162            self.account = account #update client's copy of account
163
164        return account

If token is provided returns specified account, otherwise token defaults to self.token. \If token is default self.account is updated

def set_content_option(self, content_id: str, option: str, value, token: str = None):
166    def set_content_option(self, content_id: str, option: str, value, token: str = None):
167        """Sets content option like 'description', 'public', etc (more at gofile.io/api).  Note that folder and file content have different options"""
168        token = self._get_token(token)
169
170        if type(value) == bool: #api expects bools to be string
171            if value:
172                value = "true"
173            else:
174                value = "false"
175
176        data = {
177            "contentId": content_id,
178            "token": token,
179            "option": option,
180            "value": value
181        }
182
183        resp = requests.put(GofileClient.API_ROUTE_SET_OPTION_URL, data=data)
184        got = GofileClient.handle_response(resp)

Sets content option like 'description', 'public', etc (more at gofile.io/api). Note that folder and file content have different options

def copy_content(self, *content_ids: str, parent_id: str = None, token: str = None):
187    def copy_content(self, *content_ids: str, parent_id: str = None, token: str = None):
188        """Copy provided content_ids to destination folder's content_id.  Currently returns None because api doesn't return any information.  Will have to query parent folder"""
189        if not parent_id:
190            raise ValueError("Must pass a parent folder id: parent_id=None")
191
192        token = self._get_token(token)
193        data = {
194            "contentsId": ",".join(content_ids),
195            "folderIdDest": parent_id,
196            "token": token
197        }
198
199        resp = requests.put(GofileClient.API_ROUTE_COPY_CONTENT_URL, data=data)
200        got = GofileClient.handle_response(resp)
201        
202        """
203        As of right now /copyContent does not return information about newly created content
204        For this reason copy_content will just return None
205
206        return GofileContent.__init_from_resp__(resp, client=self)
207        """

Copy provided content_ids to destination folder's content_id. Currently returns None because api doesn't return any information. Will have to query parent folder

def create_folder(self, name: str, parent_id: str, token: str = None):
209    def create_folder(self, name: str, parent_id: str, token: str = None):
210        """Creates folder in specified parent folder's content_id"""
211        token = self._get_token(token)
212
213        data = {
214            "parentFolderId": parent_id,
215            "folderName": name
216        }
217
218        data["token"] = token
219
220        resp = requests.put(GofileClient.API_ROUTE_CREATE_FOLDER_URL, data=data)
221        got = GofileClient.handle_response(resp)
222
223        return GofileContent.__init_from_resp__(resp, client=self) 

Creates folder in specified parent folder's content_id

class GofileContent:
277class GofileContent (object):
278    name: str
279    """Name of content"""
280    content_id: str
281    """Gofile API's unique content id"""
282    parent_id: str
283    """Content_id of parent folder"""
284    _type: str
285    """GofileContent subtypes, either 'file', 'folder' or 'unknown'."""
286    is_file_type: bool
287    """If GofileContent is a file"""
288    is_folder_type: bool
289    """If GofileContent is a folder"""
290    is_unknown_type: bool
291    """If GofileContent is unknown (call reload() to update)"""
292    is_deleted: bool
293    """If content is deleted (will only register if called by it's own method delete())"""
294
295    def __init__(self, content_id: str, parent_id: str, _type: str = None, client: GofileClient = None):
296        self.content_id = content_id
297        self.parent_id = parent_id
298        self._type = _type
299        self._client = client
300        self._raw = {}
301
302        self.is_file_type = _type == "file"
303        self.is_folder_type = _type == "folder"
304        self.is_unknown_type = _type == None
305        self.name = None
306        self.time_created = None
307        self.is_deleted = False
308
309    def __repr__ (self) -> str:
310        _type = self._type
311        if not _type:
312            _type = "Unknown"
313
314        return "<Gofile {}: content_id={} name={}>".format(_type.upper(), self.content_id, self.name)
315
316    def delete (self) -> None:
317        """Deletes itself.  When called successfully is_deleted = True"""
318        guest_token = self._raw.get("guestToken")
319        self._client.delete(self.content_id, token=guest_token)
320        self.is_deleted = True
321
322    def copy_to (self, dest_id: str) -> None:
323        """Copies itself to destination folder's content_id"""
324        self._client.copy_content(self.content_id, parent_id=dest_id)
325
326    def copy (self, dest_id: str) -> None:
327        self.copy_to(dest_id)
328
329    def set_option(self, option: str, value, reload: bool = True) -> None:
330        """Sets options for itself.  Full option list available at gofile.io/api"""
331        self._client.set_content_option(self.content_id, option, value)
332        if reload:
333            self.reload() #reload to get up to date information
334
335    def reload (self):
336        """Reloads any new updates to content.  If is_unknown_type = True then reload() returns updated content"""
337        if self.is_folder_type or (self.is_unknown_type and self.parent_id == None):
338            resp, data = self._client._get_content_raw_resp(self.content_id)
339
340            if self.is_unknown_type:
341                content = GofileContent.__init_from_resp__(resp, client=self._client)
342
343            elif self.is_folder_type:
344                self._override_from_dict(data)
345                return self
346        
347        elif (self.is_unknown_type or self.is_file_type) and self.parent_id:
348            resp, data = self._client._get_content_raw_resp(self.parent_id)
349            content_data = data["contents"].get(self.content_id, None)
350            content = None
351            if content_data:
352                content = GofileContent.__init_from_resp__({"data": content_data}, client=self._client)
353
354                if self.is_file_type:
355                    self._override_from_dict(content_data)
356
357            return content
358
359        else:
360            raise NotImplemented
361
362    @staticmethod
363    def __init_from_resp__ (resp: requests.Response, _type: str = None, client: GofileClient = None):
364        if type(resp) == requests.models.Response:
365            resp = resp.json()
366            
367        got = resp["data"] 
368        _type = got.get("type", _type)
369        content = None
370
371        if _type == "file":
372            content = GofileFile._load_from_dict(got, client=client)
373        elif _type == "folder":
374            content = GofileFolder._load_from_dict(got, client=client)
375        else:
376            raise NotImplemented
377
378        return content
GofileContent( content_id: str, parent_id: str, _type: str = None, client: GofileClient = None)
295    def __init__(self, content_id: str, parent_id: str, _type: str = None, client: GofileClient = None):
296        self.content_id = content_id
297        self.parent_id = parent_id
298        self._type = _type
299        self._client = client
300        self._raw = {}
301
302        self.is_file_type = _type == "file"
303        self.is_folder_type = _type == "folder"
304        self.is_unknown_type = _type == None
305        self.name = None
306        self.time_created = None
307        self.is_deleted = False
name: str

Name of content

content_id: str

Gofile API's unique content id

parent_id: str

Content_id of parent folder

is_file_type: bool

If GofileContent is a file

is_folder_type: bool

If GofileContent is a folder

is_unknown_type: bool

If GofileContent is unknown (call reload() to update)

is_deleted: bool

If content is deleted (will only register if called by it's own method delete())

time_created
def delete(self) -> None:
316    def delete (self) -> None:
317        """Deletes itself.  When called successfully is_deleted = True"""
318        guest_token = self._raw.get("guestToken")
319        self._client.delete(self.content_id, token=guest_token)
320        self.is_deleted = True

Deletes itself. When called successfully is_deleted = True

def copy_to(self, dest_id: str) -> None:
322    def copy_to (self, dest_id: str) -> None:
323        """Copies itself to destination folder's content_id"""
324        self._client.copy_content(self.content_id, parent_id=dest_id)

Copies itself to destination folder's content_id

def copy(self, dest_id: str) -> None:
326    def copy (self, dest_id: str) -> None:
327        self.copy_to(dest_id)
def set_option(self, option: str, value, reload: bool = True) -> None:
329    def set_option(self, option: str, value, reload: bool = True) -> None:
330        """Sets options for itself.  Full option list available at gofile.io/api"""
331        self._client.set_content_option(self.content_id, option, value)
332        if reload:
333            self.reload() #reload to get up to date information

Sets options for itself. Full option list available at gofile.io/api

def reload(self):
335    def reload (self):
336        """Reloads any new updates to content.  If is_unknown_type = True then reload() returns updated content"""
337        if self.is_folder_type or (self.is_unknown_type and self.parent_id == None):
338            resp, data = self._client._get_content_raw_resp(self.content_id)
339
340            if self.is_unknown_type:
341                content = GofileContent.__init_from_resp__(resp, client=self._client)
342
343            elif self.is_folder_type:
344                self._override_from_dict(data)
345                return self
346        
347        elif (self.is_unknown_type or self.is_file_type) and self.parent_id:
348            resp, data = self._client._get_content_raw_resp(self.parent_id)
349            content_data = data["contents"].get(self.content_id, None)
350            content = None
351            if content_data:
352                content = GofileContent.__init_from_resp__({"data": content_data}, client=self._client)
353
354                if self.is_file_type:
355                    self._override_from_dict(content_data)
356
357            return content
358
359        else:
360            raise NotImplemented

Reloads any new updates to content. If is_unknown_type = True then reload() returns updated content