Refactoring: Twitter webhook spec.

This commit is contained in:
Ryan Lue 2018-12-18 11:40:56 +01:00 committed by Thorsten Eckel
parent 7326122e28
commit 2077fac218
7 changed files with 899 additions and 740 deletions

View file

@ -9,19 +9,23 @@ FactoryBot.define do
created_by_id 1 created_by_id 1
factory :twitter_channel do factory :twitter_channel do
transient do
custom_options { {} }
end
area 'Twitter::Account' area 'Twitter::Account'
options do options do
{ {
adapter: 'twitter', adapter: 'twitter',
auth: { auth: {
consumer_key: 'some', consumer_key: 'some',
consumer_secret: 'some', consumer_secret: 'some',
oauth_token: 'key', oauth_token: 'key',
oauth_token_secret: 'secret', oauth_token_secret: 'secret',
}, },
user: { user: {
screen_name: 'system_login',
id: 'system_id', id: 'system_id',
screen_name: 'system_login',
}, },
sync: { sync: {
import_older_tweets: true, import_older_tweets: true,
@ -29,21 +33,21 @@ FactoryBot.define do
search: [ search: [
{ {
term: 'zammad', term: 'zammad',
group_id: Group.first.id, group_id: Group.first.id
}, },
{ {
term: 'hash_tag1', term: 'hash_tag1',
group_id: Group.first.id, group_id: Group.first.id
}, }
], ],
mentions: { mentions: {
group_id: Group.first.id, group_id: Group.first.id
}, },
direct_messages: { direct_messages: {
group_id: Group.first.id, group_id: Group.first.id
} }
} }
} }.deep_merge(custom_options)
end end
end end
end end

View file

@ -1,282 +1,412 @@
require 'rails_helper' require 'rails_helper'
RSpec.describe 'Integration Twitter Webhook', type: :request do RSpec.describe 'Twitter Webhook Integration', type: :request do
let!(:external_credential) { create(:twitter_credential, credentials: credentials) }
let(:credentials) { { consumer_key: 'CCC', consumer_secret: 'DDD' } }
let(:agent_user) do describe '#webhook_verify (for Twitter to confirm Zammads credentials)' do
create(:agent_user) context 'with only cached credentials' do
let!(:external_credential) { nil }
before { Cache.write('external_credential_twitter', credentials) }
it 'returns an HMAC signature for cached credentials plus params[:crc_token]' do
get '/api/v1/channels_twitter_webhook',
params: { crc_token: 'some_random', nonce: 'some_nonce' },
headers: { 'x-twitter-webhooks-signature' => 'something' },
as: :json
expect(response).to have_http_status(200)
expect(json_response).to include('response_token' => 'sha256=VE19eUk6krbdSqWPdvH71xtFhApBAU81uPW3UT65vOs=')
end
end
context 'with only credentials stored in DB' do
it 'returns an HMAC signature for stored credentials plus params[:crc_token]' do
get '/api/v1/channels_twitter_webhook',
params: { crc_token: 'some_random', nonce: 'some_nonce' },
headers: { 'x-twitter-webhooks-signature' => 'something' },
as: :json
expect(response).to have_http_status(200)
expect(json_response).to include('response_token' => 'sha256=VE19eUk6krbdSqWPdvH71xtFhApBAU81uPW3UT65vOs=')
end
end
end end
before(:each) do describe '#webhook_incoming' do
@external_credential = ExternalCredential.create!( let!(:channel) do
name: 'twitter', create(
credentials: { :twitter_channel,
consumer_key: 'CCC', custom_options: {
consumer_secret: 'DDD', auth: {
} external_credential_id: external_credential.id,
) oauth_token: 'AAA',
@channel = Channel.create!( oauth_token_secret: 'BBB',
group_id: nil, consumer_key: 'CCC',
area: 'Twitter::Account', consumer_secret: 'DDD',
options: { },
adapter: 'twitter', user: {
user: { id: 123,
id: 123, name: 'Zammad HQ',
screen_name: 'zammadhq', screen_name: 'zammadhq',
name: 'Zammad HQ', },
}, sync: {
auth: { limit: 20,
external_credential_id: 1, track_retweets: false,
oauth_token: 'AAA', search: [
oauth_token_secret: 'BBB', {
consumer_key: 'CCC', term: '#zammad', group_id: Group.first.id.to_s
consumer_secret: 'DDD', },
}, {
sync: { term: '#hello1234', group_id: Group.first.id.to_s
limit: 20, }
search: [{ term: '#zammad', group_id: Group.first.id.to_s }, { term: '#hello1234', group_id: Group.first.id.to_s }], ],
mentions: { group_id: Group.first.id.to_s }, }
direct_messages: { group_id: Group.first.id.to_s },
track_retweets: false
} }
}, )
active: true,
updated_by_id: 1,
created_by_id: 1,
)
end
describe 'request verify' do
it 'does at config check' do
Cache.write('external_credential_twitter', @external_credential.credentials)
@external_credential.destroy
params = {
crc_token: 'some_random',
nonce: 'some_nonce',
}
get '/api/v1/channels_twitter_webhook', params: params, headers: { 'x-twitter-webhooks-signature' => 'something' }, as: :json
expect(response).to have_http_status(200)
expect(json_response['response_token']).to eq('sha256=VE19eUk6krbdSqWPdvH71xtFhApBAU81uPW3UT65vOs=')
end end
it 'does configured check' do describe 'confirming authenticity of incoming Twitter webhook' do
Cache.delete('external_credential_twitter') context 'with valid headers & parameters' do
params = { it 'returns 200 success' do
crc_token: 'some_random', post '/api/v1/channels_twitter_webhook',
nonce: 'some_nonce', params: { for_user_id: channel.options[:user][:id], key: 'value' },
} headers: { 'x-twitter-webhooks-signature' => 'sha256=JjEmBe1lVKT8XldrYUKibF+D5ehp8f0jDk3PXZSHEWI=' },
get '/api/v1/channels_twitter_webhook', params: params, headers: { 'x-twitter-webhooks-signature' => 'something' }, as: :json as: :json
expect(response).to have_http_status(200)
expect(json_response['response_token']).to eq('sha256=VE19eUk6krbdSqWPdvH71xtFhApBAU81uPW3UT65vOs=') expect(response).to have_http_status(200)
end
end
context 'when request is missing x-twitter-webhooks-signature header' do
it 'returns 422 with error message' do
post '/api/v1/channels_twitter_webhook', as: :json
expect(response).to have_http_status(422)
expect(json_response).to include('error' => "Missing 'x-twitter-webhooks-signature' header")
end
end
context 'when Zammad has no Twitter credentials (in DB or cache)' do
let!(:external_credential) { nil }
let!(:channel) { nil }
it 'returns 422 with error message' do
post '/api/v1/channels_twitter_webhook',
headers: { 'x-twitter-webhooks-signature' => 'something' },
as: :json
expect(response).to have_http_status(422)
expect(json_response).to include('error' => "No such external_credential 'twitter'!")
end
end
context 'with invalid token in x-twitter-webhooks-signature header' do
it 'returns 401 with error message' do
post '/api/v1/channels_twitter_webhook',
headers: { 'x-twitter-webhooks-signature' => 'something' },
as: :json
expect(response).to have_http_status(401)
expect(json_response).to include('error' => 'Not authorized')
end
end
context 'with :for_user_id request parameter' do
it 'returns 422 with error message' do
post '/api/v1/channels_twitter_webhook',
params: { key: 'value' },
headers: { 'x-twitter-webhooks-signature' => 'sha256=EERHBy/k17v+SuT+K0OXuwhJtKnPtxi0n/Y4Wye4kVU=' },
as: :json
expect(response).to have_http_status(422)
expect(json_response).to include('error' => "Missing 'for_user_id' in payload!")
end
end
context 'with invalid :for_user_id request parameter' do
it 'returns 422 with error message' do
post '/api/v1/channels_twitter_webhook',
params: { for_user_id: 'not_existing', key: 'value' },
headers: { 'x-twitter-webhooks-signature' => 'sha256=QaJiQl/4WRp/GF37b+eAdF6kPgptjDCLOgAIIbB1s0I=' },
as: :json
expect(response).to have_http_status(422)
expect(json_response).to include('error' => "No such channel for user id 'not_existing'!")
end
end
end end
end describe 'auto-creation of tickets/articles on webhook receipt' do
let(:webhook_payload) do
JSON.parse(File.read(Rails.root.join('test', 'data', 'twitter', payload_file))).symbolize_keys
end
describe 'request incoming - base' do context 'for outbound DMs' do
context 'not matching any admin-defined filters' do
let(:payload_file) { 'webhook1_direct_message.json' }
it 'does without x-twitter-webhooks-signature header check' do it 'returns 200' do
params = {} post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json
post '/api/v1/channels_twitter_webhook', params: params, as: :json
expect(response).to have_http_status(422) expect(response).to have_http_status(200)
expect(json_response['error']).to eq('Missing \'x-twitter-webhooks-signature\' header') end
it 'creates closed ticket' do
expect { post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json }
.to change { Ticket.count }.by(1)
expect(Ticket.last.attributes)
.to include(
'title' => 'Hey! Hello!',
'state_id' => Ticket::State.find_by(name: 'closed').id,
'priority_id' => Ticket::Priority.find_by(name: '2 normal').id,
'customer_id' => User.find_by(login: 'zammadhq', firstname: 'Zammad', lastname: 'Hq').id,
'created_by_id' => User.find_by(login: 'zammadhq', firstname: 'Zammad', lastname: 'Hq').id
)
end
it 'creates first article on closed ticket' do
expect { post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json }
.to change { Ticket::Article.count }.by(1)
expect(Ticket::Article.last.attributes)
.to include(
'from' => '@zammadhq',
'to' => '@medenhofer',
'message_id' => '1062015437679050760',
'created_by_id' => User.find_by(login: 'zammadhq', firstname: 'Zammad', lastname: 'Hq').id
)
end
it 'does not add any attachments to newly created ticket' do
post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json
expect(Ticket::Article.last.attachments).to be_empty
end
end
end
context 'for inbound DMs' do
context 'matching admin-defined #hashtag filter, with a link to an image' do
let(:payload_file) { 'webhook2_direct_message.json' }
it 'returns 200' do
post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json
expect(response).to have_http_status(200)
end
it 'creates new ticket' do
expect { post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json }
.to change { Ticket.count }.by(1)
expect(Ticket.last.attributes)
.to include(
'title' => 'Hello Zammad #zammad @znuny Yeah! https://t.co/UfaCwi9cUb',
'state_id' => Ticket::State.find_by(name: 'new').id,
'priority_id' => Ticket::Priority.find_by(name: '2 normal').id,
'customer_id' => User.find_by(login: 'medenhofer', firstname: 'Martin', lastname: 'Edenhofer').id,
)
end
it 'creates first article on new ticket' do
expect { post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json }
.to change { Ticket::Article.count }.by(1)
expect(Ticket::Article.last.attributes)
.to include(
'to' => '@zammadhq',
'from' => '@medenhofer',
'body' => "Hello Zammad #zammad @znuny\n\nYeah! https://twitter.com/messages/media/1063077238797725700",
'message_id' => '1063077238797725700',
'created_by_id' => User.find_by(login: 'medenhofer', firstname: 'Martin', lastname: 'Edenhofer').id
)
end
it 'does not add linked image as attachment to newly created ticket' do
post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json
expect(Ticket::Article.last.attachments).to be_empty
end
end
context 'from same sender as previously imported DMs' do
let(:payload_file) { 'webhook3_direct_message.json' }
before { post '/api/v1/channels_twitter_webhook', **previous_webhook_payload, as: :json }
let(:previous_webhook_payload) do
JSON.parse(File.read(Rails.root.join('test', 'data', 'twitter', 'webhook2_direct_message.json'))).symbolize_keys
end
it 'returns 200' do
post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json
expect(response).to have_http_status(200)
end
it 'does not create new ticket' do
expect { post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json }
.not_to change { Ticket.count }
end
it 'adds new article to existing, open ticket' do
expect { post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json }
.to change { Ticket::Article.count }.by(1)
expect(Ticket::Article.last.attributes)
.to include(
'to' => '@zammadhq',
'from' => '@medenhofer',
'body' => 'Hello again!',
'message_id' => '1063077238797725701',
'created_by_id' => User.find_by(login: 'medenhofer', firstname: 'Martin', lastname: 'Edenhofer').id,
'ticket_id' => Ticket.find_by(title: 'Hello Zammad #zammad @znuny Yeah! https://t.co/UfaCwi9cUb').id
)
end
it 'does not add any attachments to newly created ticket' do
post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json
expect(Ticket::Article.last.attachments).to be_empty
end
end
end
context 'when receiving duplicate DMs' do
let(:payload_file) { 'webhook1_direct_message.json' }
it 'still returns 200' do
2.times { post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json }
expect(response).to have_http_status(200)
end
it 'does not create duplicate articles' do
expect do
2.times { post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json }
end.to change { Ticket::Article.count }.by(1)
end
end
context 'for tweets' do
context 'matching admin-defined #hashtag filter, with an image link' do
let(:payload_file) { 'webhook1_tweet.json' }
before do
stub_request(:get, 'http://pbs.twimg.com/profile_images/785412960797745152/wxdIvejo_bigger.jpg')
.to_return(status: 200, body: 'some_content')
stub_request(:get, 'https://pbs.twimg.com/media/DsFKfJRWkAAFEbo.jpg')
.to_return(status: 200, body: 'some_content')
end
it 'returns 200' do
post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json
expect(response).to have_http_status(200)
end
it 'creates a closed ticket' do
expect { post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json }
.to change { Ticket.count }.by(1)
expect(Ticket.last.attributes)
.to include(
'title' => 'Hey @medenhofer ! #hello1234 https://t.co/f1kffFlwpN',
'state_id' => Ticket::State.find_by(name: 'closed').id,
'priority_id' => Ticket::Priority.find_by(name: '2 normal').id,
'customer_id' => User.find_by(login: 'zammadhq', firstname: 'Zammad', lastname: 'Hq').id,
'created_by_id' => User.find_by(login: 'zammadhq', firstname: 'Zammad', lastname: 'Hq').id,
)
end
it 'creates first article on closed ticket' do
expect { post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json }
.to change { Ticket::Article.count }.by(1)
expect(Ticket::Article.last.attributes)
.to include(
'from' => '@zammadhq',
'to' => '@medenhofer',
'body' => 'Hey @medenhofer ! #hello1234 https://twitter.com/zammadhq/status/1063212927510081536/photo/1',
'message_id' => '1063212927510081536',
'created_by_id' => User.find_by(login: 'zammadhq', firstname: 'Zammad', lastname: 'Hq').id
)
end
it 'add linked image as attachment to newly created article' do
expect(Ticket::Article.last.attachments)
.to match_array(Store.where(filename: 'DsFKfJRWkAAFEbo.jpg'))
end
end
context 'longer than 140 characters (with no media links)' do
let(:payload_file) { 'webhook2_tweet.json' }
before do
stub_request(:get, 'http://pbs.twimg.com/profile_images/794220000450150401/D-eFg44R_bigger.jpg')
.to_return(status: 200, body: 'some_content')
end
it 'returns 200' do
post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json
expect(response).to have_http_status(200)
end
it 'creates a new ticket' do
expect { post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json }
.to change { Ticket.count }.by(1)
expect(Ticket.last.attributes)
.to include(
'title' => '@znuny Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy ...',
'state_id' => Ticket::State.find_by(name: 'new').id,
'priority_id' => Ticket::Priority.find_by(name: '2 normal').id,
'customer_id' => User.find_by(login: 'medenhofer', firstname: 'Martin', lastname: 'Edenhofer').id,
'created_by_id' => User.find_by(login: 'medenhofer', firstname: 'Martin', lastname: 'Edenhofer').id,
)
end
it 'creates first article on new ticket' do
expect { post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json }
.to change { Ticket::Article.count }.by(1)
expect(Ticket::Article.last.attributes)
.to include(
'from' => '@medenhofer',
'to' => '@znuny',
'body' => '@znuny Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lore',
'created_by_id' => User.find_by(login: 'medenhofer', firstname: 'Martin', lastname: 'Edenhofer').id,
'message_id' => '1065035365336141825'
)
end
it 'does not add any attachments to newly created ticket' do
post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json
expect(Ticket::Article.last.attachments).to be_empty
end
end
context 'when receiving duplicate messages' do
let(:payload_file) { 'webhook1_tweet.json' }
it 'still returns 200' do
2.times { post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json }
expect(response).to have_http_status(200)
end
it 'does not create duplicate articles' do
expect do
2.times { post '/api/v1/channels_twitter_webhook', **webhook_payload, as: :json }
end.to change { Ticket::Article.count }.by(1)
end
end
end
end end
it 'does no external_credential check' do
@external_credential.destroy
params = {}
post '/api/v1/channels_twitter_webhook', params: params, headers: { 'x-twitter-webhooks-signature' => 'something' }, as: :json
expect(response).to have_http_status(422)
expect(json_response['error']).to eq('No such external_credential \'twitter\'!')
end
it 'does invalid token check' do
params = {}
post '/api/v1/channels_twitter_webhook', params: params, headers: { 'x-twitter-webhooks-signature' => 'something' }, as: :json
expect(response).to have_http_status(401)
expect(json_response['error']).to eq('Not authorized')
end
it 'does existing for_user_id check' do
params = { key: 'value' }
post '/api/v1/channels_twitter_webhook', params: params, headers: { 'x-twitter-webhooks-signature' => 'sha256=EERHBy/k17v+SuT+K0OXuwhJtKnPtxi0n/Y4Wye4kVU=' }, as: :json
expect(response).to have_http_status(422)
expect(json_response['error']).to eq('Missing \'for_user_id\' in payload!')
end
it 'does invalid user check' do
params = { for_user_id: 'not_existing', key: 'value' }
post '/api/v1/channels_twitter_webhook', params: params, headers: { 'x-twitter-webhooks-signature' => 'sha256=QaJiQl/4WRp/GF37b+eAdF6kPgptjDCLOgAIIbB1s0I=' }, as: :json
expect(response).to have_http_status(422)
expect(json_response['error']).to eq('No such channel for user id \'not_existing\'!')
end
it 'does valid token check' do
params = { for_user_id: 123, key: 'value' }
post '/api/v1/channels_twitter_webhook', params: params, headers: { 'x-twitter-webhooks-signature' => 'sha256=JjEmBe1lVKT8XldrYUKibF+D5ehp8f0jDk3PXZSHEWI=' }, as: :json
expect(response).to have_http_status(200)
end
end
describe 'request incoming direct message' do
it 'create new ticket via tweet' do
post '/api/v1/channels_twitter_webhook', params: read_messaage('webhook1_direct_message'), headers: { 'x-twitter-webhooks-signature' => 'sha256=xXu7qrPhqXfo8Ot14c0si9HrdQdBNru5fkSdoMZi+Ms=' }, as: :json
expect(response).to have_http_status(200)
article = Ticket::Article.find_by(message_id: '1062015437679050760')
expect(article).to be_present
expect(article.from).to eq('@zammadhq')
expect(article.to).to eq('@medenhofer')
expect(article.created_by.login).to eq('zammadhq')
expect(article.created_by.firstname).to eq('Zammad')
expect(article.created_by.lastname).to eq('Hq')
expect(article.attachments.count).to eq(0)
ticket = article.ticket
expect(ticket.title).to eq('Hey! Hello!')
expect(ticket.state.name).to eq('closed')
expect(ticket.priority.name).to eq('2 normal')
expect(ticket.customer.login).to eq('zammadhq')
expect(ticket.customer.firstname).to eq('Zammad')
expect(ticket.customer.lastname).to eq('Hq')
expect(ticket.created_by.login).to eq('zammadhq')
expect(ticket.created_by.firstname).to eq('Zammad')
expect(ticket.created_by.lastname).to eq('Hq')
post '/api/v1/channels_twitter_webhook', params: read_messaage('webhook2_direct_message'), headers: { 'x-twitter-webhooks-signature' => 'sha256=wYiCk7gfAgrnerCpj3XD58hozfVDjcQvcYPZCFH+stU=' }, as: :json
article = Ticket::Article.find_by(message_id: '1063077238797725700')
expect(article).to be_present
expect(article.to).to eq('@zammadhq')
expect(article.from).to eq('@medenhofer')
expect(article.body).to eq("Hello Zammad #zammad @znuny\n\nYeah! https://twitter.com/messages/media/1063077238797725700")
expect(article.created_by.login).to eq('medenhofer')
expect(article.created_by.firstname).to eq('Martin')
expect(article.created_by.lastname).to eq('Edenhofer')
expect(article.attachments.count).to eq(0)
ticket = article.ticket
expect(ticket.title).to eq('Hello Zammad #zammad @znuny Yeah! https://t.co/UfaCwi9cUb')
expect(ticket.state.name).to eq('new')
expect(ticket.priority.name).to eq('2 normal')
expect(ticket.customer.login).to eq('medenhofer')
expect(ticket.customer.firstname).to eq('Martin')
expect(ticket.customer.lastname).to eq('Edenhofer')
post '/api/v1/channels_twitter_webhook', params: read_messaage('webhook3_direct_message'), headers: { 'x-twitter-webhooks-signature' => 'sha256=OTguUdchBdxNal/csZsRkytKL5srrUuezZ3hp/2E404=' }, as: :json
article = Ticket::Article.find_by(message_id: '1063077238797725701')
expect(article).to be_present
expect(article.to).to eq('@zammadhq')
expect(article.from).to eq('@medenhofer')
expect(article.body).to eq('Hello again!')
expect(article.created_by.login).to eq('medenhofer')
expect(article.created_by.firstname).to eq('Martin')
expect(article.created_by.lastname).to eq('Edenhofer')
expect(article.ticket.id).to eq(ticket.id)
expect(article.attachments.count).to eq(0)
ticket = article.ticket
expect(ticket.title).to eq('Hello Zammad #zammad @znuny Yeah! https://t.co/UfaCwi9cUb')
expect(ticket.state.name).to eq('new')
expect(ticket.priority.name).to eq('2 normal')
expect(ticket.customer.login).to eq('medenhofer')
expect(ticket.customer.firstname).to eq('Martin')
expect(ticket.customer.lastname).to eq('Edenhofer')
end
it 'check duplicate' do
post '/api/v1/channels_twitter_webhook', params: read_messaage('webhook1_direct_message'), headers: { 'x-twitter-webhooks-signature' => 'sha256=xXu7qrPhqXfo8Ot14c0si9HrdQdBNru5fkSdoMZi+Ms=' }, as: :json
expect(response).to have_http_status(200)
post '/api/v1/channels_twitter_webhook', params: read_messaage('webhook1_direct_message'), headers: { 'x-twitter-webhooks-signature' => 'sha256=xXu7qrPhqXfo8Ot14c0si9HrdQdBNru5fkSdoMZi+Ms=' }, as: :json
expect(response).to have_http_status(200)
expect(Ticket::Article.where(message_id: '1062015437679050760').count).to eq(1)
end
end
describe 'request incoming direct message' do
it 'create new ticket via tweet' do
stub_request(:get, 'http://pbs.twimg.com/profile_images/785412960797745152/wxdIvejo_bigger.jpg')
.to_return(status: 200, body: 'some_content')
stub_request(:get, 'https://pbs.twimg.com/media/DsFKfJRWkAAFEbo.jpg')
.to_return(status: 200, body: 'some_content')
post '/api/v1/channels_twitter_webhook', params: read_messaage('webhook1_tweet'), headers: { 'x-twitter-webhooks-signature' => 'sha256=DmARpz6wdgte6Vj+ePeqC+RHvEDokmwOIIqr4//utkk=' }, as: :json
expect(response).to have_http_status(200)
article = Ticket::Article.find_by(message_id: '1063212927510081536')
expect(article).to be_present
expect(article.from).to eq('@zammadhq')
expect(article.to).to eq('@medenhofer')
expect(article.body).to eq('Hey @medenhofer ! #hello1234 https://twitter.com/zammadhq/status/1063212927510081536/photo/1')
expect(article.created_by.login).to eq('zammadhq')
expect(article.created_by.firstname).to eq('Zammad')
expect(article.created_by.lastname).to eq('Hq')
expect(article.attachments.count).to eq(1)
expect(article.attachments[0].filename).to eq('DsFKfJRWkAAFEbo.jpg')
ticket = article.ticket
expect(ticket.title).to eq('Hey @medenhofer ! #hello1234 https://t.co/f1kffFlwpN')
expect(ticket.state.name).to eq('closed')
expect(ticket.priority.name).to eq('2 normal')
expect(ticket.customer.login).to eq('zammadhq')
expect(ticket.customer.firstname).to eq('Zammad')
expect(ticket.customer.lastname).to eq('Hq')
expect(ticket.created_by.login).to eq('zammadhq')
expect(ticket.created_by.firstname).to eq('Zammad')
expect(ticket.created_by.lastname).to eq('Hq')
end
it 'create new ticket via tweet extended_tweet' do
stub_request(:get, 'http://pbs.twimg.com/profile_images/794220000450150401/D-eFg44R_bigger.jpg')
.to_return(status: 200, body: 'some_content')
post '/api/v1/channels_twitter_webhook', params: read_messaage('webhook2_tweet'), headers: { 'x-twitter-webhooks-signature' => 'sha256=U7bglX19JitI2xuvyONAc0d/fowIFEeUzkEgnWdGyUM=' }, as: :json
expect(response).to have_http_status(200)
article = Ticket::Article.find_by(message_id: '1065035365336141825')
expect(article).to be_present
expect(article.from).to eq('@medenhofer')
expect(article.to).to eq('@znuny')
expect(article.body).to eq('@znuny Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lore')
expect(article.created_by.login).to eq('medenhofer')
expect(article.created_by.firstname).to eq('Martin')
expect(article.created_by.lastname).to eq('Edenhofer')
expect(article.attachments.count).to eq(0)
ticket = article.ticket
expect(ticket.title).to eq('@znuny Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy ...')
expect(ticket.state.name).to eq('new')
expect(ticket.priority.name).to eq('2 normal')
expect(ticket.customer.login).to eq('medenhofer')
expect(ticket.customer.firstname).to eq('Martin')
expect(ticket.customer.lastname).to eq('Edenhofer')
expect(ticket.created_by.login).to eq('medenhofer')
expect(ticket.created_by.firstname).to eq('Martin')
expect(ticket.created_by.lastname).to eq('Edenhofer')
end
it 'check duplicate' do
post '/api/v1/channels_twitter_webhook', params: read_messaage('webhook1_tweet'), headers: { 'x-twitter-webhooks-signature' => 'sha256=DmARpz6wdgte6Vj+ePeqC+RHvEDokmwOIIqr4//utkk=' }, as: :json
expect(response).to have_http_status(200)
post '/api/v1/channels_twitter_webhook', params: read_messaage('webhook1_tweet'), headers: { 'x-twitter-webhooks-signature' => 'sha256=DmARpz6wdgte6Vj+ePeqC+RHvEDokmwOIIqr4//utkk=' }, as: :json
expect(response).to have_http_status(200)
expect(Ticket::Article.where(message_id: '1063212927510081536').count).to eq(1)
end
end
def read_messaage(file)
JSON.parse(File.read(Rails.root.join('test', 'data', 'twitter', "#{file}.json")))
end end
end end

View file

@ -1,65 +1,70 @@
{ {
"for_user_id": "123", "headers" : {
"direct_message_events": [ "x-twitter-webhooks-signature" : "sha256=xXu7qrPhqXfo8Ot14c0si9HrdQdBNru5fkSdoMZi+Ms="
{ },
"type": "message_create", "params" : {
"id": "1062015437679050760", "for_user_id": "123",
"created_timestamp": "1542039186292", "direct_message_events": [
"message_create": { {
"target": { "type": "message_create",
"recipient_id": "456" "id": "1062015437679050760",
}, "created_timestamp": "1542039186292",
"sender_id": "123", "message_create": {
"source_app_id": "268278", "target": {
"message_data": { "recipient_id": "456"
"text": "Hey! Hello!", },
"entities": { "sender_id": "123",
"hashtags": [], "source_app_id": "268278",
"symbols": [], "message_data": {
"user_mentions": [], "text": "Hey! Hello!",
"urls": [] "entities": {
"hashtags": [],
"symbols": [],
"user_mentions": [],
"urls": []
}
} }
} }
} }
} ],
], "apps": {
"apps": { "268278": {
"268278": { "id": "268278",
"id": "268278", "name": "Twitter Web Client",
"name": "Twitter Web Client", "url": "http://twitter.com"
"url": "http://twitter.com" }
}
},
"users": {
"123": {
"id": "123",
"created_timestamp": "1476091912921",
"name": "Zammad HQ",
"screen_name": "zammadhq",
"description": "Helpdesk and Customer Support made easy. Open Source for download or to go with SaaS. #zammad",
"url": "https://t.co/XITyrXmhTP",
"protected": false,
"verified": false,
"followers_count": 426,
"friends_count": 509,
"statuses_count": 436,
"profile_image_url": "http://pbs.twimg.com/profile_images/785412960797745152/wxdIvejo_normal.jpg",
"profile_image_url_https": "https://pbs.twimg.com/profile_images/785412960797745152/wxdIvejo_normal.jpg"
}, },
"456": { "users": {
"id": "456", "123": {
"created_timestamp": "1290730789000", "id": "123",
"name": "Martin Edenhofer", "created_timestamp": "1476091912921",
"screen_name": "medenhofer", "name": "Zammad HQ",
"description": "Open Source professional and geek. Also known as #OTRS and #Zammad inventor. ;)\r\nEntrepreneur and Advisor for open source people in need.", "screen_name": "zammadhq",
"url": "https://t.co/whm4HTWdMw", "description": "Helpdesk and Customer Support made easy. Open Source for download or to go with SaaS. #zammad",
"protected": false, "url": "https://t.co/XITyrXmhTP",
"verified": false, "protected": false,
"followers_count": 312, "verified": false,
"friends_count": 314, "followers_count": 426,
"statuses_count": 222, "friends_count": 509,
"profile_image_url": "http://pbs.twimg.com/profile_images/794220000450150401/D-eFg44R_normal.jpg", "statuses_count": 436,
"profile_image_url_https": "https://pbs.twimg.com/profile_images/794220000450150401/D-eFg44R_normal.jpg" "profile_image_url": "http://pbs.twimg.com/profile_images/785412960797745152/wxdIvejo_normal.jpg",
"profile_image_url_https": "https://pbs.twimg.com/profile_images/785412960797745152/wxdIvejo_normal.jpg"
},
"456": {
"id": "456",
"created_timestamp": "1290730789000",
"name": "Martin Edenhofer",
"screen_name": "medenhofer",
"description": "Open Source professional and geek. Also known as #OTRS and #Zammad inventor. ;)\r\nEntrepreneur and Advisor for open source people in need.",
"url": "https://t.co/whm4HTWdMw",
"protected": false,
"verified": false,
"followers_count": 312,
"friends_count": 314,
"statuses_count": 222,
"profile_image_url": "http://pbs.twimg.com/profile_images/794220000450150401/D-eFg44R_normal.jpg",
"profile_image_url_https": "https://pbs.twimg.com/profile_images/794220000450150401/D-eFg44R_normal.jpg"
}
} }
} }
} }

View file

@ -1,162 +1,167 @@
{ {
"for_user_id": "123", "headers": {
"tweet_create_events": [ "x-twitter-webhooks-signature": "sha256=DmARpz6wdgte6Vj+ePeqC+RHvEDokmwOIIqr4//utkk="
{ },
"created_at": "Thu Nov 15 23:31:30 +0000 2018", "params": {
"id": 1063212927510081536, "for_user_id": "123",
"id_str": "1063212927510081536", "tweet_create_events": [
"text": "Hey @medenhofer ! #hello1234 https://t.co/f1kffFlwpN", {
"display_text_range": [0, 29], "created_at": "Thu Nov 15 23:31:30 +0000 2018",
"source": "<a href=\"http://twitter.com\" rel=\"nofollow\">Twitter Web Client</a>", "id": 1063212927510081536,
"truncated": false, "id_str": "1063212927510081536",
"in_reply_to_status_id": null, "text": "Hey @medenhofer ! #hello1234 https://t.co/f1kffFlwpN",
"in_reply_to_status_id_str": null, "display_text_range": [0, 29],
"in_reply_to_user_id": null, "source": "<a href=\"http://twitter.com\" rel=\"nofollow\">Twitter Web Client</a>",
"in_reply_to_user_id_str": null, "truncated": false,
"in_reply_to_screen_name": null, "in_reply_to_status_id": null,
"user": { "in_reply_to_status_id_str": null,
"id": 123, "in_reply_to_user_id": null,
"id_str": "123", "in_reply_to_user_id_str": null,
"name": "Zammad HQ", "in_reply_to_screen_name": null,
"screen_name": "zammadhq", "user": {
"location": null, "id": 123,
"url": "http://zammad.com", "id_str": "123",
"description": "Helpdesk and Customer Support made easy. Open Source for download or to go with SaaS. #zammad", "name": "Zammad HQ",
"translator_type": "none", "screen_name": "zammadhq",
"protected": false, "location": null,
"verified": false, "url": "http://zammad.com",
"followers_count": 427, "description": "Helpdesk and Customer Support made easy. Open Source for download or to go with SaaS. #zammad",
"friends_count": 512, "translator_type": "none",
"listed_count": 20, "protected": false,
"favourites_count": 280, "verified": false,
"statuses_count": 438, "followers_count": 427,
"created_at": "Mon Oct 10 09:31:52 +0000 2016", "friends_count": 512,
"utc_offset": null, "listed_count": 20,
"time_zone": null, "favourites_count": 280,
"geo_enabled": false, "statuses_count": 438,
"lang": "en", "created_at": "Mon Oct 10 09:31:52 +0000 2016",
"contributors_enabled": false, "utc_offset": null,
"is_translator": false, "time_zone": null,
"profile_background_color": "000000", "geo_enabled": false,
"profile_background_image_url": "http://abs.twimg.com/images/themes/theme1/bg.png", "lang": "en",
"profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme1/bg.png", "contributors_enabled": false,
"profile_background_tile": false, "is_translator": false,
"profile_link_color": "31B068", "profile_background_color": "000000",
"profile_sidebar_border_color": "000000", "profile_background_image_url": "http://abs.twimg.com/images/themes/theme1/bg.png",
"profile_sidebar_fill_color": "000000", "profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme1/bg.png",
"profile_text_color": "000000", "profile_background_tile": false,
"profile_use_background_image": false, "profile_link_color": "31B068",
"profile_image_url": "http://pbs.twimg.com/profile_images/785412960797745152/wxdIvejo_normal.jpg", "profile_image_url_https": "https://pbs.twimg.com/profile_images/785412960797745152/wxdIvejo_normal.jpg", "profile_sidebar_border_color": "000000",
"profile_banner_url": "https://pbs.twimg.com/profile_banners/123/1476097853", "profile_sidebar_fill_color": "000000",
"default_profile": false, "profile_text_color": "000000",
"default_profile_image": false, "profile_use_background_image": false,
"following": null, "profile_image_url": "http://pbs.twimg.com/profile_images/785412960797745152/wxdIvejo_normal.jpg", "profile_image_url_https": "https://pbs.twimg.com/profile_images/785412960797745152/wxdIvejo_normal.jpg",
"follow_request_sent": null, "profile_banner_url": "https://pbs.twimg.com/profile_banners/123/1476097853",
"notifications": null "default_profile": false,
}, "default_profile_image": false,
"geo": null, "following": null,
"coordinates": null, "follow_request_sent": null,
"place": null, "notifications": null
"contributors": null, },
"is_quote_status": false, "geo": null,
"quote_count": 0, "coordinates": null,
"reply_count": 0, "place": null,
"retweet_count": 0, "contributors": null,
"favorite_count": 0, "is_quote_status": false,
"entities": { "quote_count": 0,
"hashtags": [ "reply_count": 0,
{"text": "hello1234", "indices": [19, 29]} "retweet_count": 0,
], "favorite_count": 0,
"urls": [], "entities": {
"user_mentions": [ "hashtags": [
{ {"text": "hello1234", "indices": [19, 29]}
"screen_name": "medenhofer", ],
"name": "Martin Edenhofer", "urls": [],
"id": 456, "user_mentions": [
"id_str": "456", {
"indices": [4, 15] "screen_name": "medenhofer",
} "name": "Martin Edenhofer",
], "id": 456,
"symbols": [], "id_str": "456",
"media": [ "indices": [4, 15]
{ }
"id": 1063212885961248768, ],
"id_str": "1063212885961248768", "symbols": [],
"indices": [30, 53], "media": [
"media_url": "http://pbs.twimg.com/media/DsFKfJRWkAAFEbo.jpg", {
"media_url_https": "https://pbs.twimg.com/media/DsFKfJRWkAAFEbo.jpg", "id": 1063212885961248768,
"url": "https://t.co/f1kffFlwpN", "id_str": "1063212885961248768",
"display_url": "pic.twitter.com/f1kffFlwpN", "indices": [30, 53],
"expanded_url": "https://twitter.com/zammadhq/status/1063212927510081536/photo/1", "media_url": "http://pbs.twimg.com/media/DsFKfJRWkAAFEbo.jpg",
"type": "photo", "media_url_https": "https://pbs.twimg.com/media/DsFKfJRWkAAFEbo.jpg",
"sizes": { "url": "https://t.co/f1kffFlwpN",
"thumb": { "display_url": "pic.twitter.com/f1kffFlwpN",
"w": 150, "expanded_url": "https://twitter.com/zammadhq/status/1063212927510081536/photo/1",
"h": 150, "type": "photo",
"resize": "crop" "sizes": {
}, "thumb": {
"large": { "w": 150,
"w": 852, "h": 150,
"h": 462, "resize": "crop"
"resize": "fit" },
}, "large": {
"medium": { "w": 852,
"w": 852, "h": 462,
"h": 462, "resize": "fit"
"resize": "fit" },
}, "medium": {
"small": { "w": 852,
"w": 680, "h": 462,
"h": 369, "resize": "fit"
"resize": "fit" },
"small": {
"w": 680,
"h": 369,
"resize": "fit"
}
} }
} }
} ]
] },
}, "extended_entities": {
"extended_entities": { "media": [
"media": [ {
{ "id": 1063212885961248768,
"id": 1063212885961248768, "id_str": "1063212885961248768",
"id_str": "1063212885961248768", "indices": [30, 53],
"indices": [30, 53], "media_url": "http://pbs.twimg.com/media/DsFKfJRWkAAFEbo.jpg",
"media_url": "http://pbs.twimg.com/media/DsFKfJRWkAAFEbo.jpg", "media_url_https": "https://pbs.twimg.com/media/DsFKfJRWkAAFEbo.jpg",
"media_url_https": "https://pbs.twimg.com/media/DsFKfJRWkAAFEbo.jpg", "url": "https://t.co/f1kffFlwpN",
"url": "https://t.co/f1kffFlwpN", "display_url": "pic.twitter.com/f1kffFlwpN",
"display_url": "pic.twitter.com/f1kffFlwpN", "expanded_url": "https://twitter.com/zammadhq/status/1063212927510081536/photo/1",
"expanded_url": "https://twitter.com/zammadhq/status/1063212927510081536/photo/1", "type": "photo",
"type": "photo", "sizes": {
"sizes": { "thumb": {
"thumb": { "w": 150,
"w": 150, "h": 150,
"h": 150, "resize": "crop"
"resize": "crop" },
}, "large": {
"large": { "w": 852,
"w": 852, "h": 462,
"h": 462, "resize": "fit"
"resize": "fit" },
}, "medium": {
"medium": { "w": 852,
"w": 852, "h": 462,
"h": 462, "resize": "fit"
"resize": "fit" },
}, "small": {
"small": { "w": 680,
"w": 680, "h": 369,
"h": 369, "resize": "fit"
"resize": "fit" }
} }
} }
} ]
] },
}, "favorited": false,
"favorited": false, "retweeted": false,
"retweeted": false, "possibly_sensitive": false,
"possibly_sensitive": false, "filter_level": "low",
"filter_level": "low", "lang": "und",
"lang": "und", "timestamp_ms": "1542324690116"
"timestamp_ms": "1542324690116" }
} ]
] }
} }

View file

@ -1,113 +1,118 @@
{ {
"for_user_id": "123", "headers" : {
"direct_message_events": [ "x-twitter-webhooks-signature" : "sha256=wYiCk7gfAgrnerCpj3XD58hozfVDjcQvcYPZCFH+stU="
{ },
"type": "message_create", "params": {
"id": "1063077238797725700", "for_user_id": "123",
"created_timestamp": "1542292339406", "direct_message_events": [
"message_create": { {
"target": { "type": "message_create",
"recipient_id": "123" "id": "1063077238797725700",
}, "created_timestamp": "1542292339406",
"sender_id": "456", "message_create": {
"message_data": { "target": {
"text": "Hello Zammad #zammad @znuny\n\nYeah! https://t.co/UfaCwi9cUb", "recipient_id": "123"
"entities": {
"hashtags": [
{
"text": "zammad",
"indices": [13,20]
}
],
"symbols": [],
"user_mentions": [
{
"screen_name": "znuny",
"name": "Znuny / ES for OTRS",
"id": 789,
"id_str": "789",
"indices": [21, 27]
}
],
"urls": [
{
"url": "https://t.co/UfaCwi9cUb",
"expanded_url": "https://twitter.com/messages/media/1063077238797725700",
"display_url": "pic.twitter.com/UfaCwi9cUb",
"indices": [35, 58]
}
]
}, },
"attachment": { "sender_id": "456",
"type": "media", "message_data": {
"media": { "text": "Hello Zammad #zammad @znuny\n\nYeah! https://t.co/UfaCwi9cUb",
"id": 1063077198536556545, "entities": {
"id_str": "1063077198536556545", "hashtags": [
"indices": [35, 58], {
"media_url": "https://ton.twitter.com/1.1/ton/data/dm/1063077238797725700/1063077198536556545/9FZgsMdV.jpg", "text": "zammad",
"media_url_https": "https://ton.twitter.com/1.1/ton/data/dm/1063077238797725700/1063077198536556545/9FZgsMdV.jpg", "indices": [13,20]
"url": "https://t.co/UfaCwi9cUb", }
"display_url": "pic.twitter.com/UfaCwi9cUb", ],
"expanded_url": "https://twitter.com/messages/media/1063077238797725700", "symbols": [],
"type": "photo", "user_mentions": [
"sizes": { {
"thumb": { "screen_name": "znuny",
"w": 150, "name": "Znuny / ES for OTRS",
"h": 150, "id": 789,
"resize": "crop" "id_str": "789",
}, "indices": [21, 27]
"medium": { }
"w": 1200, ],
"h": 313, "urls": [
"resize": "fit" {
}, "url": "https://t.co/UfaCwi9cUb",
"small": { "expanded_url": "https://twitter.com/messages/media/1063077238797725700",
"w": 680, "display_url": "pic.twitter.com/UfaCwi9cUb",
"h": 177, "indices": [35, 58]
"resize": "fit" }
}, ]
"large": { },
"w": 1472, "attachment": {
"h": 384, "type": "media",
"resize": "fit" "media": {
"id": 1063077198536556545,
"id_str": "1063077198536556545",
"indices": [35, 58],
"media_url": "https://ton.twitter.com/1.1/ton/data/dm/1063077238797725700/1063077198536556545/9FZgsMdV.jpg",
"media_url_https": "https://ton.twitter.com/1.1/ton/data/dm/1063077238797725700/1063077198536556545/9FZgsMdV.jpg",
"url": "https://t.co/UfaCwi9cUb",
"display_url": "pic.twitter.com/UfaCwi9cUb",
"expanded_url": "https://twitter.com/messages/media/1063077238797725700",
"type": "photo",
"sizes": {
"thumb": {
"w": 150,
"h": 150,
"resize": "crop"
},
"medium": {
"w": 1200,
"h": 313,
"resize": "fit"
},
"small": {
"w": 680,
"h": 177,
"resize": "fit"
},
"large": {
"w": 1472,
"h": 384,
"resize": "fit"
}
} }
} }
} }
} }
} }
} }
} ],
], "users": {
"users": { "456": {
"456": { "id": "456",
"id": "456", "created_timestamp": "1290730789000",
"created_timestamp": "1290730789000", "name": "Martin Edenhofer",
"name": "Martin Edenhofer", "screen_name": "medenhofer",
"screen_name": "medenhofer", "description": "Open Source professional and geek. Also known as #OTRS and #Zammad inventor. ;)\r\nEntrepreneur and Advisor for open source people in need.",
"description": "Open Source professional and geek. Also known as #OTRS and #Zammad inventor. ;)\r\nEntrepreneur and Advisor for open source people in need.", "url": "https://t.co/whm4HTWdMw",
"url": "https://t.co/whm4HTWdMw", "protected": false,
"protected": false, "verified": false,
"verified": false, "followers_count": 312,
"followers_count": 312, "friends_count": 314,
"friends_count": 314, "statuses_count": 222,
"statuses_count": 222, "profile_image_url": "http://pbs.twimg.com/profile_images/794220000450150401/D-eFg44R_normal.jpg",
"profile_image_url": "http://pbs.twimg.com/profile_images/794220000450150401/D-eFg44R_normal.jpg", "profile_image_url_https": "https://pbs.twimg.com/profile_images/794220000450150401/D-eFg44R_normal.jpg"
"profile_image_url_https": "https://pbs.twimg.com/profile_images/794220000450150401/D-eFg44R_normal.jpg" },
}, "123": {
"123": { "id": "123",
"id": "123", "created_timestamp": "1476091912921",
"created_timestamp": "1476091912921", "name": "Zammad HQ",
"name": "Zammad HQ", "screen_name": "zammadhq",
"screen_name": "zammadhq", "description": "Helpdesk and Customer Support made easy. Open Source for download or to go with SaaS. #zammad",
"description": "Helpdesk and Customer Support made easy. Open Source for download or to go with SaaS. #zammad", "url": "https://t.co/XITyrXmhTP",
"url": "https://t.co/XITyrXmhTP", "protected": false,
"protected": false, "verified": false,
"verified": false, "followers_count": 427,
"followers_count": 427, "friends_count": 512,
"friends_count": 512, "statuses_count": 437,
"statuses_count": 437, "profile_image_url": "http://pbs.twimg.com/profile_images/785412960797745152/wxdIvejo_normal.jpg",
"profile_image_url": "http://pbs.twimg.com/profile_images/785412960797745152/wxdIvejo_normal.jpg", "profile_image_url_https": "https://pbs.twimg.com/profile_images/785412960797745152/wxdIvejo_normal.jpg"
"profile_image_url_https": "https://pbs.twimg.com/profile_images/785412960797745152/wxdIvejo_normal.jpg" }
} }
} }
} }

View file

@ -1,68 +1,98 @@
{ {
"for_user_id": "123", "headers": {
"tweet_create_events": [ "x-twitter-webhooks-signature": "sha256=U7bglX19JitI2xuvyONAc0d/fowIFEeUzkEgnWdGyUM="
{ },
"created_at": "Wed Nov 21 00:13:13 +0000 2018", "params": {
"id": 1065035365336141825, "for_user_id": "123",
"id_str": "1065035365336141825", "tweet_create_events": [
"text": "@znuny Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et… https://t.co/b9woj0QXNZ", {
"source": "<a href=\"http://twitter.com\" rel=\"nofollow\">Twitter Web Client</a>", "created_at": "Wed Nov 21 00:13:13 +0000 2018",
"truncated": true, "id": 1065035365336141825,
"in_reply_to_status_id": null, "id_str": "1065035365336141825",
"in_reply_to_status_id_str": null, "text": "@znuny Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et… https://t.co/b9woj0QXNZ",
"in_reply_to_user_id": 123, "source": "<a href=\"http://twitter.com\" rel=\"nofollow\">Twitter Web Client</a>",
"in_reply_to_user_id_str": "123", "truncated": true,
"in_reply_to_screen_name": "znuny", "in_reply_to_status_id": null,
"user": { "in_reply_to_status_id_str": null,
"id": 219826253, "in_reply_to_user_id": 123,
"id_str": "219826253", "in_reply_to_user_id_str": "123",
"name": "Martin Edenhofer", "in_reply_to_screen_name": "znuny",
"screen_name": "medenhofer", "user": {
"location": null, "id": 219826253,
"url": "http://edenhofer.de/", "id_str": "219826253",
"description": "Open Source professional and geek. Also known as #OTRS and #Zammad inventor. ;)\r\nEntrepreneur and Advisor for open source people in need.", "name": "Martin Edenhofer",
"translator_type": "regular", "screen_name": "medenhofer",
"protected": false, "location": null,
"verified": false, "url": "http://edenhofer.de/",
"followers_count": 310, "description": "Open Source professional and geek. Also known as #OTRS and #Zammad inventor. ;)\r\nEntrepreneur and Advisor for open source people in need.",
"friends_count": 314, "translator_type": "regular",
"listed_count": 16, "protected": false,
"favourites_count": 129, "verified": false,
"statuses_count": 225, "followers_count": 310,
"created_at": "Fri Nov 26 00:19:49 +0000 2010", "friends_count": 314,
"utc_offset": null, "listed_count": 16,
"time_zone": null, "favourites_count": 129,
"geo_enabled": false, "statuses_count": 225,
"lang": "en", "created_at": "Fri Nov 26 00:19:49 +0000 2010",
"contributors_enabled": false, "utc_offset": null,
"is_translator": false, "time_zone": null,
"profile_background_color": "C0DEED", "geo_enabled": false,
"profile_background_image_url": "http://abs.twimg.com/images/themes/theme1/bg.png", "lang": "en",
"profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme1/bg.png", "contributors_enabled": false,
"profile_background_tile": true, "profile_link_color": "0084B4", "profile_sidebar_border_color": "FFFFFF", "is_translator": false,
"profile_sidebar_fill_color": "DDEEF6", "profile_background_color": "C0DEED",
"profile_text_color": "333333", "profile_background_image_url": "http://abs.twimg.com/images/themes/theme1/bg.png",
"profile_use_background_image": true, "profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme1/bg.png",
"profile_image_url": "http://pbs.twimg.com/profile_images/794220000450150401/D-eFg44R_normal.jpg", "profile_background_tile": true, "profile_link_color": "0084B4", "profile_sidebar_border_color": "FFFFFF",
"profile_image_url_https": "https://pbs.twimg.com/profile_images/794220000450150401/D-eFg44R_normal.jpg", "profile_sidebar_fill_color": "DDEEF6",
"profile_banner_url": "https://pbs.twimg.com/profile_banners/219826253/1349428277", "profile_text_color": "333333",
"default_profile": false, "profile_use_background_image": true,
"default_profile_image": false, "profile_image_url": "http://pbs.twimg.com/profile_images/794220000450150401/D-eFg44R_normal.jpg",
"following": null, "profile_image_url_https": "https://pbs.twimg.com/profile_images/794220000450150401/D-eFg44R_normal.jpg",
"follow_request_sent": null, "profile_banner_url": "https://pbs.twimg.com/profile_banners/219826253/1349428277",
"notifications": null "default_profile": false,
}, "default_profile_image": false,
"geo": null, "following": null,
"coordinates": null, "follow_request_sent": null,
"place": null, "notifications": null
"contributors": null, },
"is_quote_status": false, "geo": null,
"extended_tweet": { "coordinates": null,
"full_text": "@znuny Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lore", "place": null,
"display_text_range": [0, 279], "contributors": null,
"is_quote_status": false,
"extended_tweet": {
"full_text": "@znuny Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lore",
"display_text_range": [0, 279],
"entities": {
"hashtags": [],
"urls": [],
"user_mentions": [
{
"screen_name": "znuny",
"name": "Znuny / ES for OTRS",
"id": 123,
"id_str": "123",
"indices": [0, 6]
}
],
"symbols": []
}
},
"quote_count": 0,
"reply_count": 0,
"retweet_count": 0,
"favorite_count": 0,
"entities": { "entities": {
"hashtags": [], "hashtags": [],
"urls": [], "urls": [
{
"url": "https://t.co/b9woj0QXNZ",
"expanded_url": "https://twitter.com/i/web/status/1065035365336141825",
"display_url": "twitter.com/i/web/status/1…",
"indices": [117, 140]
}
],
"user_mentions": [ "user_mentions": [
{ {
"screen_name": "znuny", "screen_name": "znuny",
@ -73,38 +103,13 @@
} }
], ],
"symbols": [] "symbols": []
} },
}, "favorited": false,
"quote_count": 0, "retweeted": false,
"reply_count": 0, "filter_level": "low",
"retweet_count": 0, "lang": "ro",
"favorite_count": 0, "timestamp_ms": "1542759193153"
"entities": { }
"hashtags": [], ]
"urls": [ }
{
"url": "https://t.co/b9woj0QXNZ",
"expanded_url": "https://twitter.com/i/web/status/1065035365336141825",
"display_url": "twitter.com/i/web/status/1…",
"indices": [117, 140]
}
],
"user_mentions": [
{
"screen_name": "znuny",
"name": "Znuny / ES for OTRS",
"id": 123,
"id_str": "123",
"indices": [0, 6]
}
],
"symbols": []
},
"favorited": false,
"retweeted": false,
"filter_level": "low",
"lang": "ro",
"timestamp_ms": "1542759193153"
}
]
} }

View file

@ -1,57 +1,62 @@
{ {
"for_user_id": "123", "headers" : {
"direct_message_events": [ "x-twitter-webhooks-signature" : "sha256=OTguUdchBdxNal/csZsRkytKL5srrUuezZ3hp/2E404="
{ },
"type": "message_create", "params" : {
"id": "1063077238797725701", "for_user_id": "123",
"created_timestamp": "1542292339406", "direct_message_events": [
"message_create": { {
"target": { "type": "message_create",
"recipient_id": "123" "id": "1063077238797725701",
}, "created_timestamp": "1542292339406",
"sender_id": "456", "message_create": {
"message_data": { "target": {
"text": "Hello again!", "recipient_id": "123"
"entities": { },
"hashtags": [], "sender_id": "456",
"symbols": [], "message_data": {
"user_mentions": [], "text": "Hello again!",
"urls": [] "entities": {
"hashtags": [],
"symbols": [],
"user_mentions": [],
"urls": []
}
} }
} }
} }
} ],
], "users": {
"users": { "456": {
"456": { "id": "456",
"id": "456", "created_timestamp": "1290730789000",
"created_timestamp": "1290730789000", "name": "Martin Edenhofer",
"name": "Martin Edenhofer", "screen_name": "medenhofer",
"screen_name": "medenhofer", "description": "Open Source professional and geek. Also known as #OTRS and #Zammad inventor. ;)\r\nEntrepreneur and Advisor for open source people in need.",
"description": "Open Source professional and geek. Also known as #OTRS and #Zammad inventor. ;)\r\nEntrepreneur and Advisor for open source people in need.", "url": "https://t.co/whm4HTWdMw",
"url": "https://t.co/whm4HTWdMw", "protected": false,
"protected": false, "verified": false,
"verified": false, "followers_count": 312,
"followers_count": 312, "friends_count": 314,
"friends_count": 314, "statuses_count": 222,
"statuses_count": 222, "profile_image_url": "http://pbs.twimg.com/profile_images/794220000450150401/D-eFg44R_normal.jpg",
"profile_image_url": "http://pbs.twimg.com/profile_images/794220000450150401/D-eFg44R_normal.jpg", "profile_image_url_https": "https://pbs.twimg.com/profile_images/794220000450150401/D-eFg44R_normal.jpg"
"profile_image_url_https": "https://pbs.twimg.com/profile_images/794220000450150401/D-eFg44R_normal.jpg" },
}, "123": {
"123": { "id": "123",
"id": "123", "created_timestamp": "1476091912921",
"created_timestamp": "1476091912921", "name": "Zammad HQ",
"name": "Zammad HQ", "screen_name": "zammadhq",
"screen_name": "zammadhq", "description": "Helpdesk and Customer Support made easy. Open Source for download or to go with SaaS. #zammad",
"description": "Helpdesk and Customer Support made easy. Open Source for download or to go with SaaS. #zammad", "url": "https://t.co/XITyrXmhTP",
"url": "https://t.co/XITyrXmhTP", "protected": false,
"protected": false, "verified": false,
"verified": false, "followers_count": 427,
"followers_count": 427, "friends_count": 512,
"friends_count": 512, "statuses_count": 437,
"statuses_count": 437, "profile_image_url": "http://pbs.twimg.com/profile_images/785412960797745152/wxdIvejo_normal.jpg",
"profile_image_url": "http://pbs.twimg.com/profile_images/785412960797745152/wxdIvejo_normal.jpg", "profile_image_url_https": "https://pbs.twimg.com/profile_images/785412960797745152/wxdIvejo_normal.jpg"
"profile_image_url_https": "https://pbs.twimg.com/profile_images/785412960797745152/wxdIvejo_normal.jpg" }
} }
} }
} }