yammdesk 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Yammdesk
2
+
3
+
4
+ ## WARNING
5
+ WARNING: THIS SOFTWARE IS ALPHA. It has bugs which may set fire to your wife. If you do not have a wife, it may seek out the equivalent and set fire to that. I strongly suggest you do not use it until this notice disappears.
6
+
7
+ ## Description
8
+
9
+ A two-way interface between Yammer, and your ticketing system. Currently only Web Help Desk is supported, but there are plans to include Zendesk.
10
+
11
+ ## Installation
12
+
13
+ $ gem install yammdesk
14
+
15
+ Use the configuration template provided in the templates folder to create a conf file in the folder you will be running the script from.
16
+
17
+ ## Usage
18
+
19
+ Run SyncTicketing.rb to sync Yammer <=> Ticketing System.
20
+
21
+ Alternatively, you can use the methods provided to write your own solution.
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/TODO ADDED
@@ -0,0 +1,8 @@
1
+ # TODO
2
+
3
+ Many things are broken ;)
4
+
5
+ * Ensure that tokens are not passed via URL
6
+ * Refactor API call methods into one method that can be called for all jobs.
7
+ * Ensure regular expressions that re-write the API call responses catch _all_ abnormal text (HTML) and re-write them to plain text.
8
+ * Look into using ['rich'] Yammer message response instead of ['plain'], to get around the Yammer user name change bug (which means that if a Yammer user changes their name, any messages they send will not be found when searching the ticketing system).
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'yammdesk'
4
+ require 'colorize'
5
+ require 'cgi'
6
+
7
+ beginning = Time.now
8
+ yamm = Yammdesk::Yammer.new
9
+ whd = Yammdesk::WHD.new
10
+ yamm_results = yamm.get
11
+ yamm_results.sort_by { |id| }
12
+
13
+
14
+ yamm_results.each { |m|
15
+ id = m['id']
16
+ replied_to_id = m["replied_to_id"]
17
+ thread_id = m["thread_id"]
18
+ body = m["body"]["plain"]
19
+
20
+ thread_exists = whd.thread_exists?(thread_id)
21
+ thread_starter = yamm.is_thread_starter?(id, thread_id)
22
+
23
+ # TODO: Write these methods
24
+ # ticket_closed = WHD.ticket_closed?(thread_id)
25
+ #
26
+ if thread_exists && !thread_starter
27
+ puts "Thread is not a thread starter, AND ticket exists".yellow
28
+ puts "thread_exists = #{thread_exists}\nthread_starter = #{thread_starter}".yellow
29
+ puts "thread_id = #{thread_id}".yellow
30
+ puts "messag id = #{id}".yellow
31
+ puts "body = #{body}\n".yellow
32
+
33
+ ticket_id = whd.search(thread_id)
34
+ if !whd.note_exists?(ticket_id, body)
35
+ puts "Thread is not a thread starter, AND ticket exists, AND note does not exist".blue
36
+ puts "thread_exists = #{thread_exists}\nthread_starter = #{thread_starter}".blue
37
+ puts "thread_id = #{thread_id}".blue
38
+ puts "messag id = #{id}".blue
39
+ puts "body = #{body}\n".blue
40
+
41
+ whd.update_ticket(ticket_id, body)
42
+ end
43
+ elsif !thread_exists && thread_starter
44
+ puts "Thread does not exist, AND is thread_starter".green
45
+ puts "thread_exists = #{thread_exists}\nthread_starter = #{thread_starter}".green
46
+ puts "thread_id = #{thread_id}".green
47
+ puts "messag id = #{id}".green
48
+ puts "body = #{body}".green
49
+
50
+ whd.create_ticket(thread_id, body)
51
+
52
+ ticket_id = whd.search(thread_id)
53
+ thread_replies = yamm.get_thread_replies(thread_id)
54
+ thread_replies.sort_by { |id| }
55
+ thread_replies.each { |m|
56
+ id = m['id']
57
+ replied_to_id = m["replied_to_id"]
58
+ thread_id = m["thread_id"]
59
+ body = m["body"]["plain"]
60
+
61
+ if !yamm.is_thread_starter?(id, thread_id)
62
+ whd.update_ticket(ticket_id, body)
63
+ end
64
+ }
65
+ elsif !thread_exists && !thread_starter
66
+ puts "Thread does not exist AND this is not a thread starter".red
67
+ puts "thread_exists = #{thread_exists}\nthread_starter = #{thread_starter}".red
68
+ puts "thread_id = #{thread_id}".red
69
+ puts "messag id = #{id}".red
70
+ puts "body = #{body}\n".red
71
+
72
+ missing_thread_starter = yamm.get_thread_starter(thread_id)
73
+
74
+ id = missing_thread_starter['id']
75
+ replied_to_id = missing_thread_starter["replied_to_id"]
76
+ thread_id = missing_thread_starter["thread_id"]
77
+ body = missing_thread_starter["body"]["plain"]
78
+
79
+ # We found a missing thread starter from a reply returned by the YAPI
80
+ # Let's create a ticket from the ID we found then:
81
+ whd.create_ticket(thread_id, body)
82
+
83
+ # ... and get all the replies
84
+ ticket_id = whd.search(thread_id)
85
+
86
+ thread_replies = yamm.get_thread_replies(thread_id)
87
+ thread_replies.sort_by { |id| }
88
+ thread_replies.each { |m|
89
+ id = m['id']
90
+ replied_to_id = m["replied_to_id"]
91
+ thread_id = m["thread_id"]
92
+ body = m["body"]["plain"]
93
+
94
+ if !yamm.is_thread_starter?(id, thread_id)
95
+ whd.update_ticket(ticket_id, body)
96
+ end
97
+ }
98
+ else
99
+ puts "Thread exists AND this is a thread starter\n".red
100
+ end
101
+ }
102
+
103
+
104
+ # Now check for updated tickets, and push to Yammer
105
+ whd_tickets = whd.get
106
+ whd_tickets.sort_by { |id| }
107
+
108
+ whd_tickets.each { |t|
109
+ yamm_id = t['shortSubject']
110
+ ticket_id = t['id']
111
+ whd_id = whd.search(yamm_id)
112
+ yamm_replies = yamm.get_thread_replies(yamm_id)
113
+ whd_notes = whd.get_notes(ticket_id)
114
+
115
+ whd_notes.each { | note |
116
+ puts "Now searching for note:\n#{note}\n".red
117
+
118
+ if yamm.reply_exists?(note, yamm_replies)
119
+ puts "Ticket: #{ticket_id}\n"
120
+ puts "Thread from WHD: #{yamm_id}\n"
121
+ puts "Nothin' doin'. The note is already a comment on Yammer:\n" + "#{note}\n\n".blue
122
+ else
123
+ puts "Ticket: #{ticket_id}\n"
124
+ puts "Thread from WHD: #{yamm_id}\n"
125
+ puts "Didn't find the note in the thread. Updating it with the new reply:\n" + "#{note}\n\n".green
126
+ yamm.update_thread(yamm_id, note)
127
+ end
128
+ }
129
+
130
+ }
131
+
132
+ puts "Application run completed in #{Time.now - beginning} seconds\n"
data/lib/yammdesk.rb ADDED
@@ -0,0 +1,15 @@
1
+ require "yammdesk/version"
2
+
3
+ module Yammdesk
4
+ require_relative 'yammdesk/yammer'
5
+ require 'yaml'
6
+ require 'net/http'
7
+ require 'openssl'
8
+ require 'uri'
9
+ require 'json'
10
+ require 'nokogiri'
11
+
12
+
13
+ OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE
14
+ CONFIG = YAML.load_file("config.yml") unless defined? CONFIG
15
+ end
@@ -0,0 +1,3 @@
1
+ module Yammdesk
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,158 @@
1
+ module Yammdesk
2
+
3
+ class WHD
4
+
5
+ require 'yaml'
6
+ require 'net/http'
7
+ require 'openssl'
8
+ require 'uri'
9
+ require 'json'
10
+ require 'colorize'
11
+
12
+ CONFIG = YAML.load_file("config.yml") unless defined? CONFIG
13
+
14
+
15
+ def initialize
16
+ @url = CONFIG['whd']['url']
17
+ @token = CONFIG['whd']['tech_token']
18
+ @uri = URI(@url)
19
+ current_dir = File.dirname(__FILE__)
20
+ @post_file = JSON.parse(IO.read("#{current_dir}/../../templates/create_ticket.json"))
21
+ @update_file = JSON.parse(IO.read("#{current_dir}/../../templates/update_ticket.json"))
22
+ end
23
+
24
+ def get
25
+ Net::HTTP.start(@uri.host, @uri.port) do |http|
26
+ response = http.get(@url + "Tickets/?list=group&apiKey=#{@token}")
27
+ # response = http.get(@url + "Tickets/?list=group", :params => {:apiKey => @token})
28
+ @data = JSON.parse(response.body)
29
+ end
30
+ end
31
+
32
+ def thread_exists?(thread_id)
33
+ url = @url + "Tickets?qualifier=(subject%3D'#{thread_id}')&apiKey=#{@token}"
34
+ uri = URI(url)
35
+
36
+ Net::HTTP.start(uri.host, uri.port) do |http|
37
+ response = http.get(url)
38
+ get_data = JSON.parse(response.body)
39
+
40
+ if get_data.empty?
41
+ return false
42
+ else
43
+ return true
44
+ end
45
+ end
46
+ end
47
+
48
+ def note_exists?(ticket_id, yamm_body)
49
+ yamm_body = CGI.unescapeHTML(yamm_body)
50
+ url = @url + "TicketNotes?jobTicketId=#{ticket_id}&apiKey=#{@token}"
51
+ uri = URI(url)
52
+
53
+ Net::HTTP.start(@uri.host, @uri.port) do |http|
54
+ response = http.get(url)
55
+ get_data = JSON.parse(response.body)
56
+
57
+ if note = get_data.find { |note| CGI.unescapeHTML(note['mobileNoteText']) }
58
+ return true
59
+ else
60
+ return false
61
+ end
62
+ end
63
+ end
64
+
65
+ def get_notes(ticket_id)
66
+ url = @url + "TicketNotes?jobTicketId=#{ticket_id}&apiKey=#{@token}"
67
+ uri = URI(url)
68
+
69
+ Net::HTTP.start(@uri.host, @uri.port) do |http|
70
+ response = http.get(url)
71
+ get_data = JSON.parse(response.body)
72
+
73
+ @whd_entries = []
74
+
75
+ get_data.each { |h|
76
+ note = CGI.unescapeHTML(h['mobileNoteText'])
77
+ note = note.gsub('<br/> ', "\n")
78
+ note = Nokogiri::HTML.parse(note)
79
+ note = note.text
80
+
81
+ @whd_entries << note
82
+ }
83
+
84
+ return @whd_entries
85
+ end
86
+ end
87
+
88
+ def search(thread_id)
89
+ url = @url + "Tickets?qualifier=(subject%3D'#{thread_id}')&apiKey=#{@token}"
90
+ uri = URI(url)
91
+
92
+ Net::HTTP.start(uri.host, uri.port) do |http|
93
+ response = http.get(url)
94
+ #puts "DEBUG: #{response.code}, #{response.body}"
95
+
96
+ get_data = JSON.parse(response.body)
97
+ #data = (get_data[0] || {}).to_hash
98
+ data = get_data[0].to_hash
99
+ ticket_id = data["id"]
100
+
101
+ return ticket_id
102
+ end
103
+ end
104
+
105
+ def put(options = {})
106
+ ticket_id = options[:ticket_id]
107
+ put_data = options[:put_data]
108
+
109
+ Net::HTTP.start(@uri.host, @uri.port) do |http|
110
+ response = http.request_put(@url + "Tickets/#{ticket_id}/?apiKey=#{@token}", put_data)
111
+ @data = JSON.parse(response.body)
112
+ end
113
+ end
114
+
115
+ def last_ticket
116
+ last_ticket = "10559118"
117
+ end
118
+
119
+ def update_ticket(ticket_id, message)
120
+ @update_file["ticketId"] = ticket_id
121
+ @update_file["jobticket"]["id"] = ticket_id
122
+ @update_file["noteText"] = message
123
+ post_data = @update_file.to_s
124
+ url = @url + "TechNotes?apiKey=#{@token}"
125
+ uri = URI(url)
126
+
127
+ request = Net::HTTP::Post.new(uri.request_uri)
128
+ request.body = post_data
129
+
130
+ response = Net::HTTP.new(uri.host, uri.port).start { |http|
131
+ res = http.request(request)
132
+ # puts "CODE: #{res.code} with BODY: #{res.body}\n".white
133
+ }
134
+ end
135
+
136
+ def create_ticket(thread_id, body)
137
+ @post_file["subject"] = "#{thread_id}"
138
+ @post_file["detail"] = body
139
+ post_data = @post_file.to_s
140
+
141
+ url = @url + "Tickets?apiKey=#{@token}"
142
+ uri = URI(url)
143
+
144
+ request = Net::HTTP::Post.new(uri.request_uri)
145
+ request.body = post_data
146
+
147
+ response = Net::HTTP.new(uri.host, uri.port).start { |http| http.request(request) }
148
+
149
+ # DEBUG
150
+ # puts "Creating ticket for thread: ".yellow + "#{thread_id}\n" + "with message: ".yellow + "#{post_data}\n" +
151
+ # "using put URL: ".yellow + "#{url}\n\n"
152
+ # DEBUG
153
+
154
+ end
155
+
156
+ end
157
+ end
158
+
@@ -0,0 +1,97 @@
1
+ module Yammdesk
2
+ class Yammer
3
+
4
+ require 'yaml'
5
+ require 'net/http'
6
+ require 'openssl'
7
+ require 'uri'
8
+ require 'json'
9
+ require 'nokogiri'
10
+ require_relative 'whd'
11
+
12
+ OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE
13
+ CONFIG = YAML.load_file("config.yml") unless defined? CONFIG
14
+
15
+
16
+ def initialize
17
+ @url = CONFIG['yammdesk']['url']
18
+ @token = CONFIG['yammdesk']['token']
19
+ @uri = URI(@url)
20
+ end
21
+
22
+
23
+ def get
24
+ Net::HTTP.start(@uri.host, @uri.port, :use_ssl => @uri.scheme == 'https') do |http|
25
+ response = http.get(@url + "/messages/in_group/872.json?oaccess_token=#{@token}")
26
+ @data = JSON.parse(response.body)
27
+
28
+ return @data['messages']
29
+ end
30
+ end
31
+
32
+ def get_thread_replies(thread_id)
33
+ Net::HTTP.start(@uri.host, @uri.port, :use_ssl => @uri.scheme == 'https') do |http|
34
+ response = http.get(@url + "/messages/in_thread/#{thread_id}.json?oaccess_token=#{@token}")
35
+ @data = JSON.parse(response.body)
36
+
37
+ return @data['messages']
38
+ end
39
+ end
40
+
41
+ def get_thread_starter(thread_id)
42
+ url = @url + "/messages/#{thread_id}.json?oaccess_token=#{@token}"
43
+ uri = URI(@url)
44
+
45
+ Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') do |http|
46
+ response = http.get(url)
47
+ @data = JSON.parse(response.body)
48
+ return @data
49
+ end
50
+ end
51
+
52
+ def reply_exists?(whd_note, yamm_replies)
53
+ if yamm_replies.find { |yamm|
54
+ body = CGI.unescapeHTML(yamm['body']['plain'])
55
+ body = body.gsub('<br/> ', "\n")
56
+ body = Nokogiri::HTML.parse(body)
57
+ body = body.text
58
+
59
+ body == whd_note
60
+ }
61
+ return true
62
+ else
63
+ return false
64
+ end
65
+ end
66
+
67
+ def is_thread_starter?(id, thread_id)
68
+ if id == thread_id
69
+ true
70
+ else
71
+ false
72
+ end
73
+ end
74
+
75
+ def update_thread(thread_id, body)
76
+ body = CGI::unescapeHTML(body)
77
+ body = body.gsub('<br/> ', "\n")
78
+ body = Nokogiri::HTML.parse(body)
79
+ body = body.text
80
+
81
+ url = @url + "/messages.json&oaccess_token=#{@token}"
82
+ uri = URI(url)
83
+
84
+ https = Net::HTTP.new(uri.host, uri.port)
85
+ https.use_ssl = true
86
+ https.verify_mode = OpenSSL::SSL::VERIFY_NONE
87
+
88
+ request = Net::HTTP::Post.new(uri.request_uri)
89
+ request.set_form_data('replied_to_id' => thread_id, 'body' => body)
90
+
91
+ response = https.request(request)
92
+ puts "DIDN'T FIND:\n#{body}\n\n".white
93
+ end
94
+
95
+
96
+ end
97
+ end