Initial commit to new repo
This commit is contained in:
parent
a267f9bfcc
commit
a5173c981b
292 changed files with 17472 additions and 0 deletions
21
app/Jobs/Job.php
Normal file
21
app/Jobs/Job.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
|
||||
abstract class Job
|
||||
{
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Queueable Jobs
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This job base class provides a central location to place any logic that
|
||||
| is shared across all of your jobs. The trait included with the class
|
||||
| provides access to the "onQueue" and "delay" queue helper methods.
|
||||
|
|
||||
*/
|
||||
|
||||
use Queueable;
|
||||
}
|
256
app/Jobs/ProcessWebMention.php
Normal file
256
app/Jobs/ProcessWebMention.php
Normal file
|
@ -0,0 +1,256 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Note;
|
||||
use Mf2\parse;
|
||||
use HTMLPurifier;
|
||||
use App\WebMention;
|
||||
use GuzzleHttp\Client;
|
||||
use HTMLPurifier_Config;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Jonnybarnes\WebmentionsParser\Parser;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
|
||||
class ProcessWebMention extends Job implements ShouldQueue
|
||||
{
|
||||
use InteractsWithQueue, SerializesModels;
|
||||
|
||||
protected $note;
|
||||
protected $source;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param \App\Note $note
|
||||
* @param string $source
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Note $note, $source)
|
||||
{
|
||||
$this->note = $note;
|
||||
$this->source = $source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @param \Jonnybarnes\WebmentionsParser\Parser $parser
|
||||
* @return void
|
||||
*/
|
||||
public function handle(Parser $parser)
|
||||
{
|
||||
$sourceURL = parse_url($this->source);
|
||||
$baseURL = $sourceURL['scheme'] . '://' . $sourceURL['host'];
|
||||
$remoteContent = $this->getRemoteContent($this->source);
|
||||
$microformats = $this->parseHTML($remoteContent, $baseURL);
|
||||
$count = WebMention::where('source', '=', $this->source)->count();
|
||||
if ($count > 0) {
|
||||
//we already have a webmention from this source
|
||||
$webmentions = WebMention::where('source', '=', $this->source)->get();
|
||||
foreach ($webmentions as $webmention) {
|
||||
//now check it still 'mentions' this target
|
||||
//we switch for each type of mention (reply/like/repost)
|
||||
switch ($webmention->type) {
|
||||
case 'reply':
|
||||
if ($parser->checkInReplyTo($microformats, $note->longurl) == false) {
|
||||
//it doesn't so delete
|
||||
$webmention->delete();
|
||||
|
||||
return true;
|
||||
}
|
||||
//webmenion is still a reply, so update content
|
||||
$content = $parser->replyContent($microformats);
|
||||
$this->saveImage($content);
|
||||
$content['reply'] = $this->filterHTML($content['reply']);
|
||||
$content = serialize($content);
|
||||
$webmention->content = $content;
|
||||
$webmention->save();
|
||||
|
||||
return true;
|
||||
break;
|
||||
case 'like':
|
||||
if ($parser->checkLikeOf($microformats, $note->longurl) == false) {
|
||||
//it doesn't so delete
|
||||
$webmention->delete();
|
||||
|
||||
return true;
|
||||
} //note we don't need to do anything if it still is a like
|
||||
break;
|
||||
case 'repost':
|
||||
if ($parser->checkRepostOf($microformats, $note->longurl) == false) {
|
||||
//it doesn't so delete
|
||||
$webmention->delete();
|
||||
|
||||
return true;
|
||||
} //again, we don't need to do anything if it still is a repost
|
||||
break;
|
||||
}//switch
|
||||
}//foreach
|
||||
}//if
|
||||
//no wemention in db so create new one
|
||||
$webmention = new WebMention();
|
||||
//check it is in fact a reply
|
||||
if ($parser->checkInReplyTo($microformats, $note->longurl)) {
|
||||
$content = $parser->replyContent($microformats);
|
||||
$this->saveImage($content);
|
||||
$content['reply'] = $this->filterHTML($content['reply']);
|
||||
$content = serialize($content);
|
||||
$webmention->source = $this->source;
|
||||
$webmention->target = $note->longurl;
|
||||
$webmention->commentable_id = $this->note->id;
|
||||
$webmention->commentable_type = 'App\Note';
|
||||
$webmention->type = 'reply';
|
||||
$webmention->content = $content;
|
||||
$webmention->save();
|
||||
|
||||
return true;
|
||||
} elseif ($parser->checkLikeOf($microformats, $note->longurl)) {
|
||||
//it is a like
|
||||
$content = $parser->likeContent($microformats);
|
||||
$this->saveImage($content);
|
||||
$content = serialize($content);
|
||||
$webmention->source = $this->source;
|
||||
$webmention->target = $note->longurl;
|
||||
$webmention->commentable_id = $this->note->id;
|
||||
$webmention->commentable_type = 'App\Note';
|
||||
$webmention->type = 'like';
|
||||
$webmention->content = $content;
|
||||
$webmention->save();
|
||||
|
||||
return true;
|
||||
} elseif ($parser->checkRepostOf($microformats, $note->longurl)) {
|
||||
//it is a repost
|
||||
$content = $parser->repostContent($microformats);
|
||||
$this->saveImage($content);
|
||||
$content = serialize($content);
|
||||
$webmention->source = $this->source;
|
||||
$webmention->target = $note->longurl;
|
||||
$webmention->commentable_id = $this->note->id;
|
||||
$webmention->commentable_type = 'App\Note';
|
||||
$webmention->type = 'repost';
|
||||
$webmention->content = $content;
|
||||
$webmention->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retreive the remote content from a URL, and caches the result.
|
||||
*
|
||||
* @param string The URL to retreive content from
|
||||
* @return string The HTML from the URL
|
||||
*/
|
||||
private function getRemoteContent($url)
|
||||
{
|
||||
$client = new Client();
|
||||
|
||||
$response = $client->get($url);
|
||||
$html = (string) $response->getBody();
|
||||
$path = storage_path() . '/HTML/' . $this->createFilenameFromURL($url);
|
||||
$this->fileForceContents($path, $html);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a file path from a URL. This is used when caching the HTML
|
||||
* response.
|
||||
*
|
||||
* @param string The URL
|
||||
* @return string The path name
|
||||
*/
|
||||
private function createFilenameFromURL($url)
|
||||
{
|
||||
$url = str_replace(['https://', 'http://'], ['', ''], $url);
|
||||
if (substr($url, -1) == '/') {
|
||||
$url = $url . 'index.html';
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a file, and create any necessary folders.
|
||||
*
|
||||
* @param string The directory to save to
|
||||
* @param binary The file to save
|
||||
*/
|
||||
private function fileForceContents($dir, $contents)
|
||||
{
|
||||
$parts = explode('/', $dir);
|
||||
$name = array_pop($parts);
|
||||
$dir = implode('/', $parts);
|
||||
if (! is_dir($dir)) {
|
||||
mkdir($dir, 0755, true);
|
||||
}
|
||||
file_put_contents("$dir/$name", $contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper function for php-mf2’s parse method.
|
||||
*
|
||||
* @param string The HTML to parse
|
||||
* @param string The base URL to resolve relative URLs in the HTML against
|
||||
* @return array The porcessed microformats
|
||||
*/
|
||||
private function parseHTML($html, $baseurl)
|
||||
{
|
||||
$microformats = \Mf2\parse((string) $html, $baseurl);
|
||||
|
||||
return $microformats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a profile image to the local cache.
|
||||
*
|
||||
* @param array source content
|
||||
* @return bool wether image was saved or not (we don’t save twitter profiles)
|
||||
*/
|
||||
public function saveImage(array $content)
|
||||
{
|
||||
$photo = $content['photo'];
|
||||
$home = $content['url'];
|
||||
//dont save pbs.twimg.com links
|
||||
if (parse_url($photo)['host'] != 'pbs.twimg.com'
|
||||
&& parse_url($photo)['host'] != 'twitter.com') {
|
||||
$client = new Client();
|
||||
try {
|
||||
$response = $client->get($photo);
|
||||
$image = $response->getBody(true);
|
||||
$path = public_path() . '/assets/profile-images/' . parse_url($home)['host'] . '/image';
|
||||
$this->fileForceContents($path, $image);
|
||||
} catch (Exception $e) {
|
||||
// we are openning and reading the default image so that
|
||||
// fileForceContent work
|
||||
$default = public_path() . '/assets/profile-images/default-image';
|
||||
$handle = fopen($default, 'rb');
|
||||
$image = fread($handle, filesize($default));
|
||||
fclose($handle);
|
||||
$path = public_path() . '/assets/profile-images/' . parse_url($home)['host'] . '/image';
|
||||
$this->fileForceContents($path, $image);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Purify HTML received from a webmention.
|
||||
*
|
||||
* @param string The HTML to be processed
|
||||
* @return string The processed HTML
|
||||
*/
|
||||
public function filterHTML($html)
|
||||
{
|
||||
$config = HTMLPurifier_Config::createDefault();
|
||||
$config->set('Cache.SerializerPath', storage_path() . '/HTMLPurifier');
|
||||
$purifier = new HTMLPurifier($config);
|
||||
|
||||
return $purifier->purify($html);
|
||||
}
|
||||
}
|
86
app/Jobs/SendWebMentions.php
Normal file
86
app/Jobs/SendWebMentions.php
Normal file
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
|
||||
class SendWebMentions extends Job implements ShouldQueue
|
||||
{
|
||||
use InteractsWithQueue, SerializesModels;
|
||||
|
||||
protected $url;
|
||||
protected $source;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($url, $source)
|
||||
{
|
||||
$this->url = $url;
|
||||
$this->source = $source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(Client $client)
|
||||
{
|
||||
$endpoint = $this->discoverWebmentionEndpoint($this->url, $client);
|
||||
if ($endpoint) {
|
||||
$client->post($endpoint, [
|
||||
'form_params' => [
|
||||
'source' => $this->source,
|
||||
'target' => $this->url,
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover if a URL has a webmention endpoint.
|
||||
*
|
||||
* @param string The URL
|
||||
* @param \GuzzleHttp\Client $client
|
||||
* @return string The webmention endpoint URL
|
||||
*/
|
||||
private function discoverWebmentionEndpoint($url, $client)
|
||||
{
|
||||
$endpoint = null;
|
||||
|
||||
$response = $client->get($url);
|
||||
//check HTTP Headers for webmention endpoint
|
||||
$links = \GuzzleHttp\Psr7\parse_header($response->getHeader('Link'));
|
||||
foreach ($links as $link) {
|
||||
if ($link['rel'] == 'webmention') {
|
||||
return trim($link[0], '<>');
|
||||
}
|
||||
}
|
||||
|
||||
//failed to find a header so parse HTML
|
||||
$html = (string) $response->getBody();
|
||||
|
||||
$mf2 = new \Mf2\Parser($html, $url);
|
||||
$rels = $mf2->parseRelsAndAlternates();
|
||||
if (array_key_exists('webmention', $rels[0])) {
|
||||
$endpoint = $rels[0]['webmention'][0];
|
||||
} elseif (array_key_exists('http://webmention.org/', $rels[0])) {
|
||||
$endpoint = $rels[0]['http://webmention.org/'][0];
|
||||
}
|
||||
if ($endpoint) {
|
||||
if (filter_var($endpoint, FILTER_VALIDATE_URL)) {
|
||||
return $endpoint;
|
||||
}
|
||||
//it must be a relative url, so resolve with php-mf2
|
||||
return $mf2->resolveUrl($endpoint);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
108
app/Jobs/SyndicateToTwitter.php
Normal file
108
app/Jobs/SyndicateToTwitter.php
Normal file
|
@ -0,0 +1,108 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use Twitter;
|
||||
use App\Note;
|
||||
use App\Contact;
|
||||
use Jonnybarnes\IndieWeb\Numbers;
|
||||
use Jonnybarnes\IndieWeb\NotePrep;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
|
||||
class SyndicateToTwitter extends Job implements ShouldQueue
|
||||
{
|
||||
use InteractsWithQueue, SerializesModels;
|
||||
|
||||
protected $note;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Note $note)
|
||||
{
|
||||
$this->note = $note;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @param \Jonnybarnes\IndieWeb\Numbers $numbers
|
||||
* @param \Jonnybarnes\IndieWeb\NotePrep $noteprep
|
||||
* @return void
|
||||
*/
|
||||
public function handle(Numbers $numbers, NotePrep $noteprep)
|
||||
{
|
||||
$noteSwappedNames = $this->swapNames($this->note->getOriginal('note'));
|
||||
$shorturl = 'https://' . config('url.shorturl') . '/t/' . $numbers->numto60($this->note->id);
|
||||
$tweet = $noteprep->createNote($noteSwappedNames, $shorturl, 140, true);
|
||||
$tweetOpts = ['status' => $tweet, 'format' => 'json'];
|
||||
if ($this->note->in_reply_to) {
|
||||
$tweetOpts['in_reply_to_status_id'] = $noteprep->replyTweetId($this->note->in_reply_to);
|
||||
}
|
||||
|
||||
/*if ($this->note->location) {
|
||||
$explode = explode(':', $this->note->location);
|
||||
$location = (count($explode) == 2) ? explode(',', $explode[0]) : explode(',', $explode);
|
||||
$lat = trim($location[0]);
|
||||
$long = trim($location[1]);
|
||||
$jsonPlaceId = Twitter::getGeoReverse(array('lat' => $lat, 'long' => $long, 'format' => 'json'));
|
||||
$parsePlaceId = json_decode($jsonPlaceId);
|
||||
$placeId = $parsePlaceId->result->places[0]->id ?: null;
|
||||
$tweetOpts['lat'] = $lat;
|
||||
$tweetOpts['long'] = $long;
|
||||
if ($placeId) {
|
||||
$tweetOpts['place_id'] = $placeId;
|
||||
}
|
||||
}*/
|
||||
|
||||
$mediaItems = $this->note->getMedia();
|
||||
if (count($mediaItems) > 0) {
|
||||
foreach ($mediaItems as $item) {
|
||||
$uploadedMedia = Twitter::uploadMedia(['media' => file_get_contents($item->getUrl())]);
|
||||
$mediaIds[] = $uploadedMedia->media_id_string;
|
||||
}
|
||||
$tweetOpts['media_ids'] = implode(',', $mediaIds);
|
||||
}
|
||||
|
||||
$responseJson = Twitter::postTweet($tweetOpts);
|
||||
$response = json_decode($responseJson);
|
||||
$tweetId = $response->id;
|
||||
$this->note->tweet_id = $tweetId;
|
||||
$this->note->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Swap @names in a note.
|
||||
*
|
||||
* When a note is being saved and we are posting it to twitter, we want
|
||||
* to swap our @local_name to Twitter’s @twitter_name so the user get’s
|
||||
* mentioned on Twitter.
|
||||
*
|
||||
* @param string $note
|
||||
* @return string $noteSwappedNames
|
||||
*/
|
||||
private function swapNames($note)
|
||||
{
|
||||
$regex = '/\[.*?\](*SKIP)(*F)|@(\w+)/'; //match @alice but not [@bob](...)
|
||||
$noteSwappedNames = preg_replace_callback(
|
||||
$regex,
|
||||
function ($matches) {
|
||||
try {
|
||||
$contact = Contact::where('nick', '=', mb_strtolower($matches[1]))->firstOrFail();
|
||||
} catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
|
||||
return '@' . $matches[1];
|
||||
}
|
||||
$twitterHandle = $contact->twitter;
|
||||
|
||||
return '@' . $twitterHandle;
|
||||
},
|
||||
$note
|
||||
);
|
||||
|
||||
return $noteSwappedNames;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue