From 698bfde83c3ebd5814eb6e2dd081d42701c7c5e5 Mon Sep 17 00:00:00 2001 From: TheLeo Date: Thu, 3 Oct 2024 17:02:17 +0000 Subject: [PATCH] Upload files to "/" --- ticket_transfer.py | 246 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 ticket_transfer.py diff --git a/ticket_transfer.py b/ticket_transfer.py new file mode 100644 index 0000000..2f5122f --- /dev/null +++ b/ticket_transfer.py @@ -0,0 +1,246 @@ +#!/usr/bin/env python3 + +################################### +# Library Inclusions +################################### + +import requests +import json +import os +import time +import sys +import base64 +import urllib.parse +import mimetypes + +################################### +# Function Definitions +################################### + +# Makes API call to target using either GET or POST and returns the JSON response +## request.post() usage (url, headers = headers, data= payload, files= files) or (url, headers= headers, json= payload) +def makeCall (target, verb, key, payload= None, files= None): + if verb.lower() == 'get': + try: + response = requests.get(target, auth=(key, 'X')) + if response.status_code != 429: + response.raise_for_status() + responseData = response.json() + if (checkRate(response)): + return makeCall(target, verb, key) + return responseData + except requests.exceptions.HTTPError as http_error: + print(f'{RED}HTTP Error:{RESET}\n{http_error}') + raise + except Exception as error: + print(f'{RED}Error:{RESET}\n{error}') + raise + elif verb.lower() == 'post': + try: + if files == None: + response = requests.post(target, auth=(key, 'X'), json= payload) + else: + response = requests.post(target, data= payload, auth=(key, 'X'), files= files) + if response.status_code != 429: + response.raise_for_status() + responseData = response.json() + if (checkRate(response)): + if files == None: + return makeCall(target, verb, key, payload) + else: + return makeCall(target, verb, key, payload, files) + return responseData + except requests.exceptions.HTTPError as http_error: + print(f'{RED}HTTP Error:{RESET}\n{http_error}') + raise + except Exception as error: + print(f'{RED}Error:{RESET}\n{error}') + raise + +# Clears out the specified number of console lines +def clear(number): + for num in range(number): + sys.stdout.write('\x1b[1A') + sys.stdout.write('\x1b[2K') + +def checkRate(response): + if 'Retry-After' in response.headers: + timer = int(response.headers.get('Retry-After')) + 5 + while timer > 0: + print(f'{YELLOW}*RATE LIMITED*{RESET}...Pausing for {timer} seconds') + time.sleep(1) + timer -= 1 + clear(1) + return True + else: + return False + +################################### +# Globals Variables +################################### +ticketArray = [] +transArray = [] +groupIdDefs_source = [] +groupIdDefs_dest = [] +contactIdDefs = [] +sourceDom = '' +destDom = '' +sourceKey = '' +destKey = '' +targetGroup = '' +targetGroupId = '' +RED = '\033[31m' +GREEN = '\033[32m' +YELLOW = '\033[33m' +RESET = '\033[0m' + +################################### +# Main Loop +################################### +if os.name == 'nt': + os.system('cls') +else: + os.system('clear') +print(' ______ ___ __ __ ') +print(' / ____/________ _____ ___ / | / / ____ / /_ ') +print(' / / / ___/ __ `/ __ `__ \______/ /| |______/ / / __ \/ __/ ') +print('/ /___/ / / /_/ / / / / / /_____/ ___ /_____/ /___/ /_/ / /_ ') +print('\____/_/_ \__,_/_/ /_/ /_/ /_/__|_| /_____/\____/\__/___ ') +print(' /_ __(_)____/ /_____ / /_ /_ __/________ _____ _____/ __/__ _____') +print(' / / / / ___/ //_/ _ \/ __/ / / / ___/ __ `/ __ \/ ___/ /_/ _ \/ ___/') +print(' / / / / /__/ ,< / __/ /_ / / / / / /_/ / / / (__ ) __/ __/ / ') +print('/_/ /_/\___/_/|_|\___/\__/ /_/ /_/ \__,_/_/ /_/____/_/ \___/_/ \n') +print('=========================================================================') + +## Getting transfer settings from user +sourceDom = input('Please enter the source domain:\n(ex. "https://your_domain.freshdesk.com")\n') +if sourceDom == '': + print(f'{RED}Invalid Domain Provided{RESET}') + sys.exit(1) +else: + clear(3) + sourceDom = "".join(sourceDom.split()) + print(f'SETTINGS:\nSource Domain: {GREEN}{sourceDom}{RESET}') +sourceKey = input('Please provide the API Key for the source account:\n(Hint: You can find this from your profile settings menu in Freshdesk)\n') +if sourceKey == '': + print(f'{RED}Invalid Key Provided{RESET}') + sys.exit(1) +else: + clear(3) + sourceKey = "".join(sourceKey.split()) +destDom = input('Please enter the destination domain:\n') +if destDom == '': + print(f'{RED}Invalid Domain Provided{RESET}') + sys.exit(1) +else: + clear(4) + destDom = "".join(destDom.split()) + print(f'Source Domain: {GREEN}{sourceDom}{RESET} || Target Domain: {GREEN}{destDom}{RESET}') +destKey = input('Please provide the API key for the destination account:\n') +if destKey == '': + print(f'{RED}Invalid Key Provided{RESET}') + sys.exit(1) +else: + clear(3) + destKey = "".join(destKey.split()) +targetGroup = input('What is the name of the Freshdesk group you want to transfer from?\n') +if targetGroup == '': + print(f'{RED}Invalid Group Name Provided{RESET}') + sys.exit(1) +else: + clear(3) + print(f'Group: {GREEN}{targetGroup}{RESET}') + print(f'=========================================================================') + + +## Building Definition Arrays +print(f'Obtaining account information...◐') +groupIdDefs_source = makeCall(sourceDom + '/api/v2/groups', 'get', sourceKey) +clear(1) +print(f'Obtaining account information...◓') +contactIdDefs = makeCall(sourceDom + '/api/v2/contacts', 'get', sourceKey) +clear(1) +print(f'Obtaining account information...◑') +groupIdDefs_dest = makeCall(destDom + '/api/v2/groups', 'get', destKey) +clear(1) +print(f'Obtaining account information...◒') +for i, group in enumerate(groupIdDefs_source): + anim = ['◐', '◓', '◑', '◒'] + if group["name"] == targetGroup: + targetGroupId = group["id"] + break + clear(1) + print(f'Obtaining account information...{anim[i % len(anim)]}') +clear(1) +print(f'Obtaining account information...{GREEN}COMPLETE{RESET}') + +## Building Ticket Array from Source +print(f'Gathering list of tickets from source...') +ticketArray = makeCall(sourceDom + '/api/v2/search/tickets?query=' + urllib.parse.urlencode(f'"group_id:\'{targetGroupId}\'"'), 'get', sourceKey) +clear(1) +print(f'Gathering list of tickets from source...{GREEN}COMPLETE{RESET}') + +## Transferring tickets to Destination +print(f'*This may take a while*') +print(f'Transferring tickets to destination account...◐') +for i, ticket in enumerate(ticketArray): + attachments = {} + anim = ['◓', '◑', '◒', '◐'] + ticketData = makeCall(sourceDom + f'/api/v2/tickets/{ticket["id"]}?include=conversations,requester', 'get', sourceKey) + for x, attachment in enumerate(ticket['attachments']): + if attachment['attachment_url']: + fileRes = requests.get(attachment.attachment_url) + if fileRes.status_code == 200: + attachments[x] = (attachment['name'], fileRes.content) + else: + print(f'{RED}File Error:{RESET}\nFailed to download {attachment["name"]}') + payload = { + 'name': ticketData["requester"]["name"], + 'email': ticketData["requester"]["email"], + 'phone': ticketData["requester"]["phone"], + 'subject': ticketData["subject"], + 'status': ticketData["status"], + 'priority': ticketData["priority"], + 'description': ticketData["description"], + 'source': ticketData["source"], + } + makeCall(destDom + '/api/v2/tickets', 'post', destKey, payload, files=attachments if attachments else None) + + ## Gaterhing conversation history from ticketData, and then updating the ticket to include them in a private note + if len(ticketData['conversations']) > 0: + convos = ''' + =====================
+ Conversation History
+ =====================
+ ''' + for convo in ticketData['conversations']: + for contact in contactIdDefs: + if contact['id'] == convo['user_id']: + username = contact['name'] + break + if convo['private'] == 'true': + convos += f'Private Note from {username}:
{convo["body"]}
---------------------------

' + elif convo['private'] == 'false' and convo['incoming'] == 'true': + convos += f'Incoming Reply from {username}:
{convo["body"]}
---------------------------

' + elif convo['private'] == 'false' and convo['incoming'] == 'false': + convos += f'Outgoing Reply from {username}:
{convo["body"]}
---------------------------

' + makeCall(destDom + f'/api/v2/tickets/{ticket['id']}/notes', 'post', destKey,{'body': convos}) + + clear(1) + print(f'Transferring tickets to destination account...{anim[i % len(anim)]}') +clear(2) +print(f'Transferring tickets to destination account...{GREEN}COMPLETE{RESET}') +print(f'{GREEN}*TICKET TRANSFER COMPLETED SUCCESSFULLY*{RESET}') +for i in range(5, 0, -1): + print(f'Exiting in {i} seconds...') + time.sleep(1) + clear(1) +sys.exit(0) + + + + + + + +