Skip to content

Commit 70c6553

Browse files
Merge pull request #91 from shutupflanders/with-videos
Added `withVideo` support to `TwitterStatusUpdate`
2 parents 1ad03a1 + 7ec3a8b commit 70c6553

File tree

9 files changed

+279
-4
lines changed

9 files changed

+279
-4
lines changed

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ PS: v.7.0.0 only supports Laravel 10 and PHP 8.1. If you have an older Laravel a
1919
- [Usage](#usage)
2020
- [Publish a Twitter status update](#publish-a-twitter-status-update)
2121
- [Publish Twitter status update with images](#publish-twitter-status-update-with-images)
22+
- [Publish Twitter status update with videos](#publish-twitter-status-update-with-videos)
23+
- [Publish Twitter status update with both images and videos](#publish-twitter-status-update-with-both-images-and-videos)
2224
- [Send a direct message](#send-a-direct-message)
2325
- [Handle multiple Twitter Accounts](#handle-multiple-twitter-accounts)
2426
- [Changelog](#changelog)
@@ -120,6 +122,32 @@ return (new TwitterStatusUpdate('Laravel notifications are awesome!'))->withImag
120122
public_path('mohamed.png')
121123
]);
122124
````
125+
### Publish Twitter status update with videos
126+
It is possible to publish videos with your status update too. You have to pass the video path to the `withVideo` method.
127+
````php
128+
public function toTwitter(mixed $notifiable): TwitterMessage
129+
{
130+
return (new TwitterStatusUpdate('Laravel notifications are awesome!'))->withVideo('video.mp4');
131+
}
132+
````
133+
If you want to use multiple videos, just pass an array of paths.
134+
````php
135+
return (new TwitterStatusUpdate('Laravel notifications are awesome!'))->withVideo([
136+
public_path('video1.mp4'),
137+
public_path('video.gif')
138+
]);
139+
````
140+
### Publish Twitter status update with both images and videos
141+
It is also possible to publish both images and videos with your status by using a mixture of the two methods.
142+
````php
143+
return (new TwitterStatusUpdate('Laravel notifications are awesome!'))->withVideo([
144+
public_path('video1.mp4'),
145+
public_path('video.gif')
146+
])->withImage([
147+
public_path('marcel.png'),
148+
public_path('mohamed.png')
149+
]);
150+
````
123151
### Publish Twitter status update in reply to another tweet
124152
Additionally, you can publish a status update in reply to another tweet. That is possible using the method `inReplyTo`.
125153
````php

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
"abraham/twitteroauth": "^5.0",
1717
"illuminate/notifications": "^10.0",
1818
"illuminate/support": "^10.0",
19-
"kylewm/brevity": "^0.2"
19+
"kylewm/brevity": "^0.2",
20+
"ext-fileinfo": "*"
2021
},
2122
"require-dev": {
2223
"mockery/mockery": "^1.3.1",

src/Exceptions/CouldNotSendNotification.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,11 @@ public static function statusUpdateTooLong(int $exceededLength): CouldNotSendNot
3030
"Couldn't post notification, because the status message was too long by ${exceededLength} character(s)."
3131
);
3232
}
33+
34+
public static function videoCouldNotBeProcessed(string $message): CouldNotSendNotification
35+
{
36+
return new static(
37+
"Couldn't post notification, Video upload failed: ".$message
38+
);
39+
}
3340
}

src/TwitterChannel.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public function send($notifiable, Notification $notification): array|object
2525

2626
$twitterMessage = $notification->toTwitter($notifiable);
2727
$twitterMessage = $this->addImagesIfGiven($twitterMessage);
28+
$twitterMessage = $this->addVideosIfGiven($twitterMessage);
2829

2930
$twitterApiResponse = $this->twitter->post(
3031
$twitterMessage->getApiEndpoint(),
@@ -76,4 +77,43 @@ private function addImagesIfGiven(TwitterMessage $twitterMessage): object
7677

7778
return $twitterMessage;
7879
}
80+
81+
/**
82+
* If it is a status update message and videos are provided, add them.
83+
*/
84+
private function addVideosIfGiven(TwitterMessage $twitterMessage): object
85+
{
86+
if (is_a($twitterMessage, TwitterStatusUpdate::class) && $twitterMessage->getVideos()) {
87+
$this->twitter->setTimeouts(10, 15);
88+
89+
$twitterMessage->videoIds = collect($twitterMessage->getVideos())->map(function (TwitterVideo $video) {
90+
$media = $this->twitter->upload('media/upload', [
91+
'media' => $video->getPath(),
92+
'media_category' => 'tweet_video',
93+
'media_type' => $video->getMimeType(),
94+
], true);
95+
96+
$status = $this->twitter->mediaStatus($media->media_id_string);
97+
98+
$safety = 30; // We don't want to wait forever, stop after 30 checks.
99+
while (($status->processing_info->state == 'pending' || $status->processing_info->state == 'in_progress') && $safety > 0) {
100+
if (isset($status->processing_info->error)) {
101+
break;
102+
}
103+
104+
sleep($status->processing_info->check_after_secs);
105+
$status = $this->twitter->mediaStatus($media->media_id_string);
106+
$safety = $safety - 1;
107+
}
108+
109+
if (isset($status->processing_info->error)) {
110+
throw CouldNotSendNotification::videoCouldNotBeProcessed($status->processing_info->error->message);
111+
}
112+
113+
return $status->media_id_string;
114+
});
115+
}
116+
117+
return $twitterMessage;
118+
}
79119
}

src/TwitterStatusUpdate.php

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
class TwitterStatusUpdate extends TwitterMessage
1010
{
1111
public ?Collection $imageIds = null;
12+
public ?Collection $videoIds = null;
1213
private ?array $images = null;
14+
private ?array $videos = null;
1315
private ?int $inReplyToStatusId = null;
1416

1517
/**
@@ -45,6 +47,22 @@ public function withImage(array|string $images): static
4547
return $this;
4648
}
4749

50+
/**
51+
* Set Twitter media files.
52+
*
53+
* @return $this
54+
*/
55+
public function withVideo(array|string $videos): static
56+
{
57+
$videos = is_array($videos) ? $videos : [$videos];
58+
59+
collect($videos)->each(function ($video) {
60+
$this->videos[] = new TwitterVideo($video);
61+
});
62+
63+
return $this;
64+
}
65+
4866
/**
4967
* Get Twitter images list.
5068
*/
@@ -53,6 +71,14 @@ public function getImages(): ?array
5371
return $this->images;
5472
}
5573

74+
/**
75+
* Get Twitter videos list.
76+
*/
77+
public function getVideos(): ?array
78+
{
79+
return $this->videos;
80+
}
81+
5682
/**
5783
* @param int $statusId
5884
* @return $this
@@ -79,8 +105,14 @@ public function getRequestBody(): array
79105
{
80106
$body = ['status' => $this->getContent()];
81107

82-
if ($this->imageIds instanceof Collection) {
83-
$body['media_ids'] = $this->imageIds->implode(',');
108+
$mediaIds = collect()
109+
->merge($this->imageIds instanceof Collection ? $this->imageIds : [])
110+
->merge($this->videoIds instanceof Collection ? $this->videoIds : [])
111+
->filter()
112+
->values();
113+
114+
if ($mediaIds->count() > 0) {
115+
$body['media_ids'] = $mediaIds->implode(',');
84116
}
85117

86118
if ($this->inReplyToStatusId) {

src/TwitterVideo.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace NotificationChannels\Twitter;
4+
5+
class TwitterVideo
6+
{
7+
public function __construct(private string $videoPath)
8+
{
9+
}
10+
11+
public function getPath(): string
12+
{
13+
return $this->videoPath;
14+
}
15+
16+
public function getMimeType(): string
17+
{
18+
return mime_content_type($this->videoPath);
19+
}
20+
}

tests/TwitterChannelTest.php

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,51 @@ public function it_can_send_a_status_update_notification_with_images()
7474
$this->channel->send(new TestNotifiable(), new TestNotificationWithImage());
7575
}
7676

77+
/** @test */
78+
public function it_can_send_a_status_update_notification_with_videos()
79+
{
80+
$media = new stdClass;
81+
$media->media_id_string = '2';
82+
83+
$status = new stdClass;
84+
$status->media_id_string = '2';
85+
$status->processing_info = new stdClass;
86+
$status->processing_info->state = 'completed';
87+
88+
$this->twitter->shouldReceive('setTimeouts')
89+
->once()
90+
->with(10, 15);
91+
92+
$this->twitter->shouldReceive('post')
93+
->once()
94+
->with(
95+
'statuses/update',
96+
['status' => 'Laravel Notification Channels are awesome!', 'media_ids' => '2'],
97+
false
98+
)
99+
->andReturn([]);
100+
101+
$this->twitter->shouldReceive('upload')
102+
->once()
103+
->with('media/upload', [
104+
'media' => public_path('video.mp4'),
105+
'media_category' => 'tweet_video',
106+
'media_type' => 'video/mp4',
107+
], true)
108+
->andReturn($media);
109+
110+
$this->twitter->shouldReceive('mediaStatus')
111+
->once()
112+
->with($media->media_id_string)
113+
->andReturn($status);
114+
115+
$this->twitter->shouldReceive('getLastHttpCode')
116+
->once()
117+
->andReturn(200);
118+
119+
$this->channel->send(new TestNotifiable(), new TestNotificationWithVideo());
120+
}
121+
77122
/** @test */
78123
public function it_can_send_a_status_update_notification_with_reply_to_status_id(): void
79124
{
@@ -118,6 +163,42 @@ public function it_throws_an_exception_when_it_could_not_send_the_notification()
118163

119164
$this->channel->send(new TestNotifiable(), new TestNotification());
120165
}
166+
167+
/** @test */
168+
public function it_throws_an_exception_when_it_could_not_send_the_notification_with_videos()
169+
{
170+
$media = new stdClass;
171+
$media->media_id_string = '2';
172+
173+
$status = new stdClass;
174+
$status->media_id_string = '2';
175+
$status->processing_info = new stdClass;
176+
$status->processing_info->state = 'failed';
177+
$status->processing_info->error = new stdClass;
178+
$status->processing_info->error->message = 'invalid media';
179+
180+
$this->twitter->shouldReceive('setTimeouts')
181+
->once()
182+
->with(10, 15);
183+
184+
$this->twitter->shouldReceive('upload')
185+
->once()
186+
->with('media/upload', [
187+
'media' => public_path('video.mp4'),
188+
'media_category' => 'tweet_video',
189+
'media_type' => 'video/mp4',
190+
], true)
191+
->andReturn($media);
192+
193+
$this->twitter->shouldReceive('mediaStatus')
194+
->once()
195+
->with($media->media_id_string)
196+
->andReturn($status);
197+
198+
$this->expectException(CouldNotSendNotification::class);
199+
200+
$this->channel->send(new TestNotifiable(), new TestNotificationWithVideo());
201+
}
121202
}
122203

123204
class TestNotifiable
@@ -168,6 +249,17 @@ public function toTwitter(mixed $notifiable): TwitterMessage
168249
}
169250
}
170251

252+
class TestNotificationWithVideo extends Notification
253+
{
254+
/**
255+
* @throws CouldNotSendNotification
256+
*/
257+
public function toTwitter(mixed $notifiable): TwitterMessage
258+
{
259+
return (new TwitterStatusUpdate('Laravel Notification Channels are awesome!'))->withVideo(public_path('video.mp4'));
260+
}
261+
}
262+
171263
class TestNotificationWithReplyToStatusId extends Notification
172264
{
173265
private int $replyToStatusId;

tests/TwitterStatusUpdateTest.php

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use NotificationChannels\Twitter\Exceptions\CouldNotSendNotification;
66
use NotificationChannels\Twitter\TwitterImage;
77
use NotificationChannels\Twitter\TwitterStatusUpdate;
8+
use NotificationChannels\Twitter\TwitterVideo;
89

910
class TwitterStatusUpdateTest extends TestCase
1011
{
@@ -48,15 +49,46 @@ public function it_accepts_array_of_image_paths(): void
4849
$this->assertEquals($imagePathsObjects, $message->getImages());
4950
}
5051

52+
/** @test */
53+
public function video_paths_parameter_is_optional(): void
54+
{
55+
$message = new TwitterStatusUpdate('myMessage');
56+
57+
$this->assertEquals(null, $message->getVideos());
58+
}
59+
60+
/** @test */
61+
public function it_accepts_one_video_path(): void
62+
{
63+
$message = (new TwitterStatusUpdate('myMessage'))->withVideo('video.mp4');
64+
65+
$this->assertEquals('myMessage', $message->getContent());
66+
$this->assertEquals([new TwitterVideo('video.mp4')], $message->getVideos());
67+
}
68+
69+
/** @test */
70+
public function it_accepts_array_of_video_paths(): void
71+
{
72+
$videoPaths = ['path1', 'path2'];
73+
$message = (new TwitterStatusUpdate('myMessage'))->withVideo($videoPaths);
74+
$videoPathsObjects = collect($videoPaths)->map(function ($video) {
75+
return new TwitterVideo($video);
76+
})->toArray();
77+
78+
$this->assertEquals('myMessage', $message->getContent());
79+
$this->assertEquals($videoPathsObjects, $message->getVideos());
80+
}
81+
5182
/** @test */
5283
public function it_constructs_a_request_body(): void
5384
{
5485
$message = new TwitterStatusUpdate('myMessage');
5586
$message->imageIds = collect([434, 435, 436]);
87+
$message->videoIds = collect([534, 535, 536]);
5688

5789
$this->assertEquals([
5890
'status' => 'myMessage',
59-
'media_ids' => '434,435,436',
91+
'media_ids' => '434,435,436,534,535,536',
6092
], $message->getRequestBody());
6193
}
6294

tests/TwitterVideoTest.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace NotificationChannels\Twitter;
4+
5+
function mime_content_type($path)
6+
{
7+
return 'video/mp4';
8+
}
9+
10+
namespace NotificationChannels\Twitter\Test;
11+
12+
use NotificationChannels\Twitter\TwitterVideo;
13+
14+
class TwitterVideoTest extends TestCase
15+
{
16+
/** @test */
17+
public function it_accepts_a_video_path_when_constructing_a_twitter_video(): void
18+
{
19+
$video = new TwitterVideo('/foo/bar/baz.mp4');
20+
$this->assertEquals('/foo/bar/baz.mp4', $video->getPath());
21+
$this->assertEquals('video/mp4', $video->getMimeType());
22+
}
23+
}

0 commit comments

Comments
 (0)