refactor: social media post fixes (#24664)

* fix: social media post fixes

* feat: post metrics and some fixes

* fix: sider issues

* fix: sider issue

* fix: reverting optional chaning statements

* fix: sider issues

* fix: review chnages

* fix: text trigger check

* fix: sider issue

(cherry picked from commit f7e0edecc9ce45ba7baa4c17b2b35f487dffccf2)
diff --git a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js
index 263005e..7aa0b77 100644
--- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js
+++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.js
@@ -2,8 +2,8 @@
 // For license information, please see license.txt
 
 frappe.ui.form.on('LinkedIn Settings', {
-	onload: function(frm){
-		if (frm.doc.session_status == 'Expired' && frm.doc.consumer_key && frm.doc.consumer_secret){
+	onload: function(frm) {
+		if (frm.doc.session_status == 'Expired' && frm.doc.consumer_key && frm.doc.consumer_secret) {
 			frappe.confirm(
 				__('Session not valid, Do you want to login?'),
 				function(){
@@ -14,8 +14,9 @@
 				}
 			);
 		}
+		frm.dashboard.set_headline(__("For more information, {0}.", [`<a target='_blank' href='https://docs.erpnext.com/docs/user/manual/en/CRM/linkedin-settings'>${__('Click here')}</a>`]));
 	},
-	refresh: function(frm){
+	refresh: function(frm) {
 		if (frm.doc.session_status=="Expired"){
 			let msg = __("Session Not Active. Save doc to login.");
 			frm.dashboard.set_headline_alert(
@@ -53,7 +54,7 @@
 			);
 		}
 	},
-	login: function(frm){
+	login: function(frm) {
 		if (frm.doc.consumer_key && frm.doc.consumer_secret){
 			frappe.dom.freeze();
 			frappe.call({
@@ -67,7 +68,7 @@
 			});
 		}
 	},
-	after_save: function(frm){
+	after_save: function(frm) {
 		frm.trigger("login");
 	}
 });
diff --git a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.json b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.json
index 9eacb00..f882e36 100644
--- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.json
+++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.json
@@ -2,6 +2,7 @@
  "actions": [],
  "creation": "2020-01-30 13:36:39.492931",
  "doctype": "DocType",
+ "documentation": "https://docs.erpnext.com/docs/user/manual/en/CRM/linkedin-settings",
  "editable_grid": 1,
  "engine": "InnoDB",
  "field_order": [
@@ -87,7 +88,7 @@
  ],
  "issingle": 1,
  "links": [],
- "modified": "2020-04-16 23:22:51.966397",
+ "modified": "2021-02-18 15:19:21.920725",
  "modified_by": "Administrator",
  "module": "CRM",
  "name": "LinkedIn Settings",
diff --git a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
index d8c6fb4..9b88d78 100644
--- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
+++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
@@ -3,11 +3,12 @@
 # For license information, please see license.txt
 
 from __future__ import unicode_literals
-import frappe, requests, json
+import frappe
+import requests
 from frappe import _
-from frappe.utils import get_site_url, get_url_to_form, get_link_to_form
+from frappe.utils import get_url_to_form
 from frappe.model.document import Document
-from frappe.utils.file_manager import get_file, get_file_path
+from frappe.utils.file_manager import get_file_path
 from six.moves.urllib.parse import urlencode
 
 class LinkedInSettings(Document):
@@ -42,11 +43,7 @@
 		self.db_set("access_token", response["access_token"])
 
 	def get_member_profile(self):
-		headers = {
-			"Authorization": "Bearer {}".format(self.access_token)
-		}
-		url = "https://api.linkedin.com/v2/me"
-		response = requests.get(url=url, headers=headers)
+		response = requests.get(url="https://api.linkedin.com/v2/me", headers=self.get_headers())
 		response = frappe.parse_json(response.content.decode())
 
 		frappe.db.set_value(self.doctype, self.name, {
@@ -55,16 +52,16 @@
 			"session_status": "Active"
 		})
 		frappe.local.response["type"] = "redirect"
-		frappe.local.response["location"] = get_url_to_form("LinkedIn Settings","LinkedIn Settings")
+		frappe.local.response["location"] = get_url_to_form("LinkedIn Settings", "LinkedIn Settings")
 
-	def post(self, text, media=None):
+	def post(self, text, title, media=None):
 		if not media:
-			return self.post_text(text)
+			return self.post_text(text, title)
 		else:
 			media_id = self.upload_image(media)
 
 			if media_id:
-				return self.post_text(text, media_id=media_id)
+				return self.post_text(text, title, media_id=media_id)
 			else:
 				frappe.log_error("Failed to upload media.","LinkedIn Upload Error")
 
@@ -82,9 +79,7 @@
 				}]
 			}
 		}
-		headers = {
-			"Authorization": "Bearer {}".format(self.access_token)
-		}
+		headers = self.get_headers()
 		response = self.http_post(url=register_url, body=body, headers=headers)
 
 		if response.status_code == 200:
@@ -100,24 +95,33 @@
 
 		return None
 
-	def post_text(self, text, media_id=None):
+	def post_text(self, text, title, media_id=None):
 		url = "https://api.linkedin.com/v2/shares"
-		headers = {
-			"X-Restli-Protocol-Version": "2.0.0",
-			"Authorization": "Bearer {}".format(self.access_token),
-			"Content-Type": "application/json; charset=UTF-8"
-		}
+		headers = self.get_headers()
+		headers["X-Restli-Protocol-Version"] = "2.0.0"
+		headers["Content-Type"] = "application/json; charset=UTF-8"
+
 		body = {
 			"distribution": {
 				"linkedInDistributionTarget": {}
 			},
 			"owner":"urn:li:organization:{0}".format(self.company_id),
-			"subject": "Test Share Subject",
+			"subject": title,
 			"text": {
 				"text": text
 			}
 		}
 
+		reference_url = self.get_reference_url(text)
+		if reference_url:
+			body["content"] = {
+				"contentEntities": [
+					{
+						"entityLocation": reference_url
+					}
+				]
+			}
+
 		if media_id:
 			body["content"]= {
 				"contentEntities": [{
@@ -141,20 +145,60 @@
 				raise
 
 		except Exception as e:
-			content = json.loads(response.content)
-
-			if response.status_code == 401:
-				self.db_set("session_status", "Expired")
-				frappe.db.commit()
-				frappe.throw(content["message"], title="LinkedIn Error - Unauthorized")
-			elif response.status_code == 403:
-				frappe.msgprint(_("You Didn't have permission to access this API"))
-				frappe.throw(content["message"], title="LinkedIn Error - Access Denied")
-			else:
-				frappe.throw(response.reason, title=response.status_code)
-
+			self.api_error(response)
+		
 		return response
 
+	def get_headers(self):
+		return {
+			"Authorization": "Bearer {}".format(self.access_token)
+		}
+
+	def get_reference_url(self, text):
+		import re
+		regex_url = r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+"
+		urls = re.findall(regex_url, text)
+		if urls:
+			return urls[0]
+
+	def delete_post(self, post_id):
+		try:
+			response = requests.delete(url="https://api.linkedin.com/v2/shares/urn:li:share:{0}".format(post_id), headers=self.get_headers())
+			if response.status_code !=200:
+				raise
+		except Exception:
+			self.api_error(response)
+	
+	def get_post(self, post_id):
+		url = "https://api.linkedin.com/v2/organizationalEntityShareStatistics?q=organizationalEntity&organizationalEntity=urn:li:organization:{0}&shares[0]=urn:li:share:{1}".format(self.company_id, post_id)
+
+		try:
+			response = requests.get(url=url, headers=self.get_headers())
+			if response.status_code !=200:
+				raise
+	
+		except Exception:
+			self.api_error(response)
+
+		response = frappe.parse_json(response.content.decode())
+		if len(response.elements):
+			return response.elements[0]
+
+		return None
+
+	def api_error(self, response):
+		content = frappe.parse_json(response.content.decode())
+
+		if response.status_code == 401:
+			self.db_set("session_status", "Expired")
+			frappe.db.commit()
+			frappe.throw(content["message"], title=_("LinkedIn Error - Unauthorized"))
+		elif response.status_code == 403:
+			frappe.msgprint(_("You didn't have permission to access this API"))
+			frappe.throw(content["message"], title=_("LinkedIn Error - Access Denied"))
+		else:
+			frappe.throw(response.reason, title=response.status_code)
+
 @frappe.whitelist(allow_guest=True)
 def callback(code=None, error=None, error_description=None):
 	if not error:
diff --git a/erpnext/crm/doctype/social_media_post/social_media_post.js b/erpnext/crm/doctype/social_media_post/social_media_post.js
index 6fb0f97..a8f5dee 100644
--- a/erpnext/crm/doctype/social_media_post/social_media_post.js
+++ b/erpnext/crm/doctype/social_media_post/social_media_post.js
@@ -1,67 +1,139 @@
 // Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
 // For license information, please see license.txt
 frappe.ui.form.on('Social Media Post', {
-    validate: function(frm){
-        if (frm.doc.twitter === 0 && frm.doc.linkedin === 0){
-            frappe.throw(__("Select atleast one Social Media from Share on."))
-        }
-        if (frm.doc.scheduled_time) {
-            let scheduled_time = new Date(frm.doc.scheduled_time);
-            let date_time = new Date();
-            if (scheduled_time.getTime() < date_time.getTime()){
-                frappe.throw(__("Invalid Scheduled Time"));
-            }
-        }
-        if (frm.doc.text?.length > 280){
-            frappe.throw(__("Length Must be less than 280."))
-        }
-    },
-	refresh: function(frm){
-        if (frm.doc.docstatus === 1){
-            if (frm.doc.post_status != "Posted"){
-                add_post_btn(frm);
-            }
-            else if (frm.doc.post_status == "Posted"){
-                frm.set_df_property('sheduled_time', 'read_only', 1);
-            }
+	validate: function(frm) {
+		if (frm.doc.twitter === 0 && frm.doc.linkedin === 0) {
+			frappe.throw(__("Select atleast one Social Media Platform to Share on."));
+		}
+		if (frm.doc.scheduled_time) {
+			let scheduled_time = new Date(frm.doc.scheduled_time);
+			let date_time = new Date();
+			if (scheduled_time.getTime() < date_time.getTime()) {
+				frappe.throw(__("Scheduled Time must be a future time."));
+			}
+		}
+		frm.trigger('validate_tweet_length');
+	},
 
-            let html='';
-            if (frm.doc.twitter){
-                let color = frm.doc.twitter_post_id ? "green" : "red";
-                let status = frm.doc.twitter_post_id ? "Posted" : "Not Posted";
-                html += `<div class="col-xs-6">
-                            <span class="indicator whitespace-nowrap ${color}"><span>Twitter : ${status} </span></span>
-                        </div>` ;
-            }
-            if (frm.doc.linkedin){
-                let color = frm.doc.linkedin_post_id ? "green" : "red";
-                let status = frm.doc.linkedin_post_id ? "Posted" : "Not Posted";
-                html += `<div class="col-xs-6">
-                            <span class="indicator whitespace-nowrap ${color}"><span>LinkedIn : ${status} </span></span>
-                        </div>` ;
-            }
-            html = `<div class="row">${html}</div>`;
-            frm.dashboard.set_headline_alert(html);
-        }
-    }
+	text: function(frm) {
+		if (frm.doc.text) {
+			frm.set_df_property('text', 'description', `${frm.doc.text.length}/280`);
+			frm.refresh_field('text');
+			frm.trigger('validate_tweet_length');
+		}
+	},
+
+	validate_tweet_length: function(frm) {
+		if (frm.doc.text && frm.doc.text.length > 280) {
+			frappe.throw(__("Tweet length Must be less than 280."));
+		}
+	},
+
+	onload: function(frm) {
+		frm.trigger('make_dashboard');
+	},
+
+	make_dashboard: function(frm) {
+		if (frm.doc.post_status == "Posted") {
+			frappe.call({
+				doc: frm.doc,
+				method: 'get_post',
+				freeze: true,
+				callback: (r) => {
+					if (!r.message) {
+						return;
+					}
+
+					let datasets = [], colors = [];
+					if (r.message && r.message.twitter) {
+						colors.push('#1DA1F2');
+						datasets.push({
+							name: 'Twitter',
+							values: [r.message.twitter.favorite_count, r.message.twitter.retweet_count]
+						});
+					}
+					if (r.message && r.message.linkedin) {
+						colors.push('#0077b5');
+						datasets.push({
+							name: 'LinkedIn',
+							values: [r.message.linkedin.totalShareStatistics.likeCount, r.message.linkedin.totalShareStatistics.shareCount]
+						});
+					}
+
+					if (datasets.length) {
+						frm.dashboard.render_graph({
+							data: {
+								labels: ['Likes', 'Retweets/Shares'],
+								datasets: datasets
+							},
+
+							title: __("Post Metrics"),
+							type: 'bar',
+							height: 300,
+							colors: colors
+						});
+					}
+				}
+			});
+		}
+	},
+
+	refresh: function(frm) {
+		frm.trigger('text');
+	
+		if (frm.doc.docstatus === 1) {
+			if (!['Posted', 'Deleted'].includes(frm.doc.post_status)) {
+				frm.trigger('add_post_btn'); 
+			}
+			if (frm.doc.post_status !='Deleted') {
+				frm.add_custom_button(('Delete Post'), function() {
+					frappe.confirm(__('Are you sure want to delete the Post from Social Media platforms?'),
+						function() {
+							frappe.call({
+								doc: frm.doc,
+								method: 'delete_post',
+								freeze: true,
+								callback: () => {
+									frm.reload_doc();
+								}
+							});
+						}
+					);
+				});
+			}
+
+			if (frm.doc.post_status !='Deleted') {
+				let html='';
+				if (frm.doc.twitter) {
+					let color = frm.doc.twitter_post_id ? "green" : "red";
+					let status = frm.doc.twitter_post_id ? "Posted" : "Not Posted";
+					html += `<div class="col-xs-6">
+								<span class="indicator whitespace-nowrap ${color}"><span>Twitter : ${status} </span></span>
+							</div>` ;
+				}
+				if (frm.doc.linkedin) {
+					let color = frm.doc.linkedin_post_id ? "green" : "red";
+					let status = frm.doc.linkedin_post_id ? "Posted" : "Not Posted";
+					html += `<div class="col-xs-6">
+								<span class="indicator whitespace-nowrap ${color}"><span>LinkedIn : ${status} </span></span>
+							</div>` ;
+				}
+				html = `<div class="row">${html}</div>`;
+				frm.dashboard.set_headline_alert(html);
+			}
+		}
+	},
+
+	add_post_btn: function(frm) {
+		frm.add_custom_button(__('Post Now'), function() {
+			frappe.call({
+				doc: frm.doc,
+				method: 'post',
+				freeze: true,
+				callback: function() {
+					frm.reload_doc();
+				}
+			});
+		});
+	}
 });
-var add_post_btn = function(frm){
-    frm.add_custom_button(('Post Now'), function(){
-        post(frm);
-    });
-}
-var post = function(frm){
-    frappe.dom.freeze();
-    frappe.call({
-        method: "erpnext.crm.doctype.social_media_post.social_media_post.publish",
-        args: {
-            doctype: frm.doc.doctype,
-            name: frm.doc.name
-        },
-        callback: function(r) {
-            frm.reload_doc();
-            frappe.dom.unfreeze();
-        }
-    })
-
-}
diff --git a/erpnext/crm/doctype/social_media_post/social_media_post.json b/erpnext/crm/doctype/social_media_post/social_media_post.json
index 0a00dca..98e78f9 100644
--- a/erpnext/crm/doctype/social_media_post/social_media_post.json
+++ b/erpnext/crm/doctype/social_media_post/social_media_post.json
@@ -3,9 +3,11 @@
  "autoname": "format: CRM-SMP-{YYYY}-{MM}-{DD}-{###}",
  "creation": "2020-01-30 11:53:13.872864",
  "doctype": "DocType",
+ "documentation": "https://docs.erpnext.com/docs/user/manual/en/CRM/social-media-post",
  "editable_grid": 1,
  "engine": "InnoDB",
  "field_order": [
+  "title",
   "campaign_name",
   "scheduled_time",
   "post_status",
@@ -30,32 +32,24 @@
    "fieldname": "text",
    "fieldtype": "Small Text",
    "label": "Tweet",
-   "mandatory_depends_on": "eval:doc.twitter ==1",
-   "show_days": 1,
-   "show_seconds": 1
+   "mandatory_depends_on": "eval:doc.twitter ==1"
   },
   {
    "fieldname": "image",
    "fieldtype": "Attach Image",
-   "label": "Image",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Image"
   },
   {
-   "default": "0",
+   "default": "1",
    "fieldname": "twitter",
    "fieldtype": "Check",
-   "label": "Twitter",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Twitter"
   },
   {
-   "default": "0",
+   "default": "1",
    "fieldname": "linkedin",
    "fieldtype": "Check",
-   "label": "LinkedIn",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "LinkedIn"
   },
   {
    "fieldname": "amended_from",
@@ -64,27 +58,22 @@
    "no_copy": 1,
    "options": "Social Media Post",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "depends_on": "eval:doc.twitter ==1",
    "fieldname": "content",
    "fieldtype": "Section Break",
-   "label": "Twitter",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Twitter"
   },
   {
    "allow_on_submit": 1,
    "fieldname": "post_status",
    "fieldtype": "Select",
    "label": "Post Status",
-   "options": "\nScheduled\nPosted\nError",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "no_copy": 1,
+   "options": "\nScheduled\nPosted\nCancelled\nDeleted\nError",
+   "read_only": 1
   },
   {
    "allow_on_submit": 1,
@@ -92,9 +81,8 @@
    "fieldtype": "Data",
    "hidden": 1,
    "label": "Twitter Post Id",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "no_copy": 1,
+   "read_only": 1
   },
   {
    "allow_on_submit": 1,
@@ -102,82 +90,69 @@
    "fieldtype": "Data",
    "hidden": 1,
    "label": "LinkedIn Post Id",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "no_copy": 1,
+   "read_only": 1
   },
   {
    "fieldname": "campaign_name",
    "fieldtype": "Link",
    "in_list_view": 1,
    "label": "Campaign",
-   "options": "Campaign",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Campaign"
   },
   {
    "fieldname": "column_break_6",
    "fieldtype": "Column Break",
-   "label": "Share On",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Share On"
   },
   {
    "fieldname": "column_break_14",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "fieldname": "tweet_preview",
-   "fieldtype": "HTML",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "HTML"
   },
   {
    "collapsible": 1,
    "depends_on": "eval:doc.linkedin==1",
    "fieldname": "linkedin_section",
    "fieldtype": "Section Break",
-   "label": "LinkedIn",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "LinkedIn"
   },
   {
    "collapsible": 1,
    "fieldname": "attachments_section",
    "fieldtype": "Section Break",
-   "label": "Attachments",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Attachments"
   },
   {
    "fieldname": "linkedin_post",
    "fieldtype": "Text",
    "label": "Post",
-   "mandatory_depends_on": "eval:doc.linkedin ==1",
-   "show_days": 1,
-   "show_seconds": 1
+   "mandatory_depends_on": "eval:doc.linkedin ==1"
   },
   {
    "fieldname": "column_break_15",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "allow_on_submit": 1,
    "fieldname": "scheduled_time",
    "fieldtype": "Datetime",
    "label": "Scheduled Time",
-   "read_only_depends_on": "eval:doc.post_status == \"Posted\"",
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only_depends_on": "eval:doc.post_status == \"Posted\""
+  },
+  {
+   "fieldname": "title",
+   "fieldtype": "Data",
+   "label": "Title",
+   "reqd": 1
   }
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-06-14 10:31:33.961381",
+ "modified": "2021-04-14 14:24:59.821223",
  "modified_by": "Administrator",
  "module": "CRM",
  "name": "Social Media Post",
@@ -228,5 +203,6 @@
  ],
  "sort_field": "modified",
  "sort_order": "DESC",
+ "title_field": "title",
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/crm/doctype/social_media_post/social_media_post.py b/erpnext/crm/doctype/social_media_post/social_media_post.py
index ed1b583..95320bf 100644
--- a/erpnext/crm/doctype/social_media_post/social_media_post.py
+++ b/erpnext/crm/doctype/social_media_post/social_media_post.py
@@ -10,17 +10,51 @@
 
 class SocialMediaPost(Document):
 	def validate(self):
+		if (not self.twitter and not self.linkedin):
+			frappe.throw(_("Select atleast one Social Media Platform to Share on."))
+
 		if self.scheduled_time:
 			current_time = frappe.utils.now_datetime()
 			scheduled_time = frappe.utils.get_datetime(self.scheduled_time)
 			if scheduled_time < current_time:
-				frappe.throw(_("Invalid Scheduled Time"))
+				frappe.throw(_("Scheduled Time must be a future time."))
+
+		if self.text and len(self.text) > 280:
+			frappe.throw(_("Tweet length must be less than 280."))
 
 	def submit(self):
 		if self.scheduled_time:
 			self.post_status = "Scheduled"
 		super(SocialMediaPost, self).submit()
+	
+	def on_cancel(self):
+		self.db_set('post_status', 'Cancelled')
 
+	@frappe.whitelist()
+	def delete_post(self):
+		if self.twitter and self.twitter_post_id:
+			twitter = frappe.get_doc("Twitter Settings")
+			twitter.delete_tweet(self.twitter_post_id)
+	
+		if self.linkedin and self.linkedin_post_id:
+			linkedin = frappe.get_doc("LinkedIn Settings")
+			linkedin.delete_post(self.linkedin_post_id)
+		
+		self.db_set('post_status', 'Deleted')
+
+	@frappe.whitelist()
+	def get_post(self):
+		response = {}
+		if self.linkedin and self.linkedin_post_id:
+			linkedin = frappe.get_doc("LinkedIn Settings")
+			response['linkedin'] = linkedin.get_post(self.linkedin_post_id)
+		if self.twitter and self.twitter_post_id:
+			twitter = frappe.get_doc("Twitter Settings")
+			response['twitter'] = twitter.get_tweet(self.twitter_post_id)
+		
+		return response
+
+	@frappe.whitelist()
 	def post(self):
 		try:
 			if self.twitter and not self.twitter_post_id:
@@ -29,28 +63,22 @@
 				self.db_set("twitter_post_id", twitter_post.id)
 			if self.linkedin and not self.linkedin_post_id:
 				linkedin = frappe.get_doc("LinkedIn Settings")
-				linkedin_post = linkedin.post(self.linkedin_post, self.image)
-				self.db_set("linkedin_post_id", linkedin_post.headers['X-RestLi-Id'].split(":")[-1])
+				linkedin_post = linkedin.post(self.linkedin_post, self.title, self.image)
+				self.db_set("linkedin_post_id", linkedin_post.headers['X-RestLi-Id'])
 			self.db_set("post_status", "Posted")
 
 		except:
 			self.db_set("post_status", "Error")
 			title = _("Error while POSTING {0}").format(self.name)
-			traceback = frappe.get_traceback()
-			frappe.log_error(message=traceback , title=title)
+			frappe.log_error(message=frappe.get_traceback(), title=title)
 
 def process_scheduled_social_media_posts():
-	posts = frappe.get_list("Social Media Post", filters={"post_status": "Scheduled", "docstatus":1}, fields= ["name", "scheduled_time","post_status"])
+	posts = frappe.get_list("Social Media Post", filters={"post_status": "Scheduled", "docstatus":1}, fields= ["name", "scheduled_time"])
 	start = frappe.utils.now_datetime()
 	end = start + datetime.timedelta(minutes=10)
 	for post in posts:
 		if post.scheduled_time:
 			post_time = frappe.utils.get_datetime(post.scheduled_time)
 			if post_time > start and post_time <= end:
-				publish('Social Media Post', post.name)
-
-@frappe.whitelist()
-def publish(doctype, name):
-	sm_post = frappe.get_doc(doctype, name)
-	sm_post.post()
-	frappe.db.commit()
+				sm_post = frappe.get_doc('Social Media Post', post.name)
+				sm_post.post()
diff --git a/erpnext/crm/doctype/social_media_post/social_media_post_list.js b/erpnext/crm/doctype/social_media_post/social_media_post_list.js
index c60b91a..a8c8272 100644
--- a/erpnext/crm/doctype/social_media_post/social_media_post_list.js
+++ b/erpnext/crm/doctype/social_media_post/social_media_post_list.js
@@ -1,10 +1,11 @@
 frappe.listview_settings['Social Media Post'] = {
-    add_fields: ["status","post_status"],
-    get_indicator: function(doc) {
-        return [__(doc.post_status), {
-            "Scheduled": "orange",
-            "Posted": "green",
-            "Error": "red"
-            }[doc.post_status]];
-        }
+	add_fields: ["status", "post_status"],
+	get_indicator: function(doc) {
+		return [__(doc.post_status), {
+			"Scheduled": "orange",
+			"Posted": "green",
+			"Error": "red",
+			"Deleted": "red"
+		}[doc.post_status]];
+	}
 }
diff --git a/erpnext/crm/doctype/twitter_settings/twitter_settings.js b/erpnext/crm/doctype/twitter_settings/twitter_settings.js
index f6f431c..112f3d4 100644
--- a/erpnext/crm/doctype/twitter_settings/twitter_settings.js
+++ b/erpnext/crm/doctype/twitter_settings/twitter_settings.js
@@ -2,7 +2,7 @@
 // For license information, please see license.txt
 
 frappe.ui.form.on('Twitter Settings', {
-	onload: function(frm){
+	onload: function(frm) {
 		if (frm.doc.session_status == 'Expired' && frm.doc.consumer_key && frm.doc.consumer_secret){
 			frappe.confirm(
 				__('Session not valid, Do you want to login?'),
@@ -14,10 +14,11 @@
 				}
 			);
 		}
+		frm.dashboard.set_headline(__("For more information, {0}.", [`<a target='_blank' href='https://docs.erpnext.com/docs/user/manual/en/CRM/twitter-settings'>${__('Click here')}</a>`]));
 	},
-	refresh: function(frm){
+	refresh: function(frm) {
 		let msg, color, flag=false;
-		if (frm.doc.session_status == "Active"){
+		if (frm.doc.session_status == "Active") {
 			msg = __("Session Active");
 			color = 'green';
 			flag = true;
@@ -28,7 +29,7 @@
 			flag = true;
 		}
 
-		if (flag){
+		if (flag) {
 			frm.dashboard.set_headline_alert(
 				`<div class="row">
 					<div class="col-xs-12">
@@ -38,7 +39,7 @@
 			);
 		}
 	},
-	login: function(frm){
+	login: function(frm) {
 		if (frm.doc.consumer_key && frm.doc.consumer_secret){
 			frappe.dom.freeze();
 			frappe.call({
@@ -52,7 +53,7 @@
 			});
 		}
 	},
-	after_save: function(frm){
+	after_save: function(frm) {
 		frm.trigger("login");
 	}
 });
diff --git a/erpnext/crm/doctype/twitter_settings/twitter_settings.json b/erpnext/crm/doctype/twitter_settings/twitter_settings.json
index 36776e5..8d05877 100644
--- a/erpnext/crm/doctype/twitter_settings/twitter_settings.json
+++ b/erpnext/crm/doctype/twitter_settings/twitter_settings.json
@@ -2,6 +2,7 @@
  "actions": [],
  "creation": "2020-01-30 10:29:08.562108",
  "doctype": "DocType",
+ "documentation": "https://docs.erpnext.com/docs/user/manual/en/CRM/twitter-settings",
  "editable_grid": 1,
  "engine": "InnoDB",
  "field_order": [
@@ -77,7 +78,7 @@
  "image_field": "profile_pic",
  "issingle": 1,
  "links": [],
- "modified": "2020-05-13 17:50:47.934776",
+ "modified": "2021-02-18 15:18:07.900031",
  "modified_by": "Administrator",
  "module": "CRM",
  "name": "Twitter Settings",
diff --git a/erpnext/crm/doctype/twitter_settings/twitter_settings.py b/erpnext/crm/doctype/twitter_settings/twitter_settings.py
index 1e1beab..4775656 100644
--- a/erpnext/crm/doctype/twitter_settings/twitter_settings.py
+++ b/erpnext/crm/doctype/twitter_settings/twitter_settings.py
@@ -32,7 +32,9 @@
 
 		try:
 			auth.get_access_token(oauth_verifier)
-			api = self.get_api(auth.access_token, auth.access_token_secret)
+			self.access_token = auth.access_token
+			self.access_token_secret = auth.access_token_secret
+			api = self.get_api()
 			user = api.me()
 			profile_pic = (user._json["profile_image_url"]).replace("_normal","")
 
@@ -50,11 +52,11 @@
 			frappe.msgprint(_("Error! Failed to get access token."))
 			frappe.throw(_('Invalid Consumer Key or Consumer Secret Key'))
 
-	def get_api(self, access_token, access_token_secret):
-		# authentication of consumer key and secret
-		auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"))
-		# authentication of access token and secret
-		auth.set_access_token(access_token, access_token_secret)
+	def get_api(self):
+		# authentication of consumer key and secret 
+		auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret")) 
+		# authentication of access token and secret 
+		auth.set_access_token(self.access_token, self.access_token_secret) 
 
 		return tweepy.API(auth)
 
@@ -68,13 +70,13 @@
 
 	def upload_image(self, media):
 		media = get_file_path(media)
-		api = self.get_api(self.access_token, self.access_token_secret)
+		api = self.get_api()
 		media = api.media_upload(media)
 
 		return media.media_id
 
 	def send_tweet(self, text, media_id=None):
-		api = self.get_api(self.access_token, self.access_token_secret)
+		api = self.get_api()
 		try:
 			if media_id:
 				response = api.update_status(status = text, media_ids = [media_id])
@@ -84,12 +86,32 @@
 			return response
 
 		except TweepError as e:
-			content = json.loads(e.response.content)
-			content = content["errors"][0]
-			if e.response.status_code == 401:
-				self.db_set("session_status", "Expired")
-				frappe.db.commit()
-			frappe.throw(content["message"],title="Twitter Error {0} {1}".format(e.response.status_code, e.response.reason))
+			self.api_error(e)
+
+	def delete_tweet(self, tweet_id):
+		api = self.get_api()
+		try: 
+			api.destroy_status(tweet_id)
+		except TweepError as e:
+			self.api_error(e)
+
+	def get_tweet(self, tweet_id):
+		api = self.get_api()
+		try: 
+			response = api.get_status(tweet_id, trim_user=True, include_entities=True)
+		except TweepError as e:
+			self.api_error(e)
+		
+		return response._json
+	
+	def api_error(self, e):
+		content = json.loads(e.response.content)
+		content = content["errors"][0]
+		if e.response.status_code == 401:
+			self.db_set("session_status", "Expired")
+			frappe.db.commit()
+		frappe.throw(content["message"],title=_("Twitter Error {0} : {1}").format(e.response.status_code, e.response.reason))
+
 
 @frappe.whitelist(allow_guest=True)
 def callback(oauth_token = None, oauth_verifier = None):