Skip to content
This repository has been archived by the owner on May 12, 2023. It is now read-only.

Commit

Permalink
Merge pull request #10 from erikaheidi/friends-circle
Browse files Browse the repository at this point in the history
New Interactions Banner
  • Loading branch information
erikaheidi committed Jul 2, 2021
2 parents a15e59c + 23f71ee commit 5dc67b4
Show file tree
Hide file tree
Showing 9 changed files with 254 additions and 23 deletions.
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# dynacover

A PHP GD + TwitterOAuth CLI app to dynamically generate Twitter header images and upload them via the API. This enables you to build cool little tricks, like showing your latest followers or sponsors, your latest content created, a qrcode to something, a progress bar for a goal, and whatever you can think of.
A PHP GD + TwitterOAuth CLI app to dynamically generate Twitter header images and optionally upload them via the API. This enables you to build cool little tricks, like showing your latest followers or sponsors, your latest content created, a qrcode to something, a progress bar for a goal, and whatever you can think of.

Other types of dynamic banners can also be generated. Dynacover uses [erikaheidi/gdaisy](https://github.com/erikaheidi/gdaisy) for image manipulation based on templates.

<p align="center">
<img src="https://user-images.githubusercontent.com/293241/120888813-b559f700-c5fa-11eb-901f-0dac22afd662.png"/>
Expand Down Expand Up @@ -154,3 +156,19 @@ This will open up a text editor. You should include the full paths to both the `
*/5 * * * * /usr/bin/php /home/erika/dynacover/dynacover cover update > /dev/null 2>&1
```

### Interactions Banner

The "interactions banner" is generated based on your recent interactions and can be limited to only include mutuals (people that follows you and you follow them back).

```shell
php dynacover generate interactions
```

For mutuals only, include the `--mutuals` flag:

```shell
php dynacover generate interactions --mutuals
```

_Please notice that the "mutuals" version may have a limited set of results after filtering your latest interactions (~200 mentions)._

110 changes: 110 additions & 0 deletions app/Command/Generate/InteractionsController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php

namespace App\Command\Generate;

use App\ImageSource\TwitterInteractionsImageSource;
use App\Service\TwitterServiceProvider;
use App\Storage;
use App\Template;
use GDaisy\Placeholder\ImagePlaceholder;
use GDaisy\PlaceholderInterface;
use Minicli\Command\CommandController;

class InteractionsController extends CommandController
{
public function handle(): int
{
//Start by building a dynamic template
$template = new Template('twitter-interactions', [
'width' => 675,
'height' => 1200,
]);

//Set up BG as first element
$template->addPlaceholder('background', new ImagePlaceholder([
'width' => $template->width,
'height' => $template->height,
'pos_x' => 0,
'pos_y' => 0,
'image' => "app/Resources/images/interactions.png"
]));

$limit = 30;
$per_line = 5;
$prefix = "int";
$avatar_size = 100;
$spacing = 25;
$line = 1;
$col = 1;

/** @var TwitterServiceProvider $twitter */
$twitter = $this->getApp()->twitter;

//get own user credentials
$owner = $twitter->client->get('/account/verify_credentials');

//place owner avatar at the center
$avatar_path = str_replace('normal', 'bigger', $owner->profile_image_url_https);
$avatar = Storage::downloadImage($avatar_path);
$template->addPlaceholder('owner', new ImagePlaceholder([
'width' => $avatar_size*1.5,
'height' => $avatar_size*1.5,
'pos_x' => ($template->width/2) - ($avatar_size*1.5/2),
'pos_y' => ($template->height/2) - ($avatar_size*1.5/2),
'image' => $avatar,
'filters' => [ "GDaisy\\Filter\\Circle" ]
]));

$source = new TwitterInteractionsImageSource();
$featured = $source->getImageList($this->getApp(), $limit, ['mutuals' => $this->hasFlag('mutuals')]);

//build the template
$linegap = ceil((count($featured) / 2) / $per_line);
$start_x = 20;
$start_y = 20;

for ($i = 1; $i <= $limit; $i++) {
$pos_x = $start_x + ($spacing*$col) + ($avatar_size*$col - $avatar_size);
$pos_y = $start_y + ($spacing*$line) + ($avatar_size*$line - $avatar_size);
$template->addPlaceholder($prefix . $i, new ImagePlaceholder([
'width' => $avatar_size,
'height' =>$avatar_size,
'pos_x' => $pos_x,
'pos_y' => $pos_y,
'filters' => [ "GDaisy\\Filter\\Circle" ]
]));

$col++;
if ($i % $per_line == 0) {
if ($line == $linegap) {
//second block starts at line 7
$line = 6;
}
$line++;
$col = 1;
}
}

//Apply template elements
/**
* @var string $key
* @var PlaceholderInterface $placeholder
*/
foreach ($template->placeholders as $key => $placeholder) {
if ($placeholder instanceof ImagePlaceholder and $placeholder->image) {
$placeholder->apply($template->getResource(), ['image_file' => $placeholder->image]);
continue;
}

if (isset($featured[$key])) {
$placeholder->apply($template->getResource(), $featured[$key]);
}
}

$save_path = Storage::root() . 'latest_header.png';
$template->write($save_path);
$this->getPrinter()->info("Finished generating cover at $save_path.");

return 0;
}
}
2 changes: 1 addition & 1 deletion app/Command/Generate/TwitterController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use App\ImageSource;
use App\Storage;
use App\Template;
use GDaisy\ImagePlaceholder;
use GDaisy\Placeholder\ImagePlaceholder;
use GDaisy\PlaceholderInterface;
use Minicli\Command\CommandController;

Expand Down
2 changes: 1 addition & 1 deletion app/ImageSource.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@

interface ImageSource
{
public function getImageList(App $app, $limit = 5): array;
public function getImageList(App $app, int $limit = 5, array $params = []): array;
}
2 changes: 1 addition & 1 deletion app/ImageSource/GhSponsorImageSource.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class GhSponsorImageSource implements ImageSource
{
static string $prefix = "sp";

public function getImageList(App $app, $limit = 5): array
public function getImageList(App $app, int $limit = 5, array $params = []): array
{
/** @var GithubServiceProvider $github */
$github = $app->github;
Expand Down
2 changes: 1 addition & 1 deletion app/ImageSource/TwitterFollowerImageSource.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class TwitterFollowerImageSource implements ImageSource
{
static string $prefix = "tw";

public function getImageList(App $app, $limit = 5): array
public function getImageList(App $app, int $limit = 5, array $params = []): array
{
/** @var TwitterServiceProvider $twitter */
$twitter = $app->twitter;
Expand Down
103 changes: 103 additions & 0 deletions app/ImageSource/TwitterInteractionsImageSource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php

namespace App\ImageSource;

use App\ImageSource;
use App\Service\TwitterServiceProvider;
use App\Storage;
use Minicli\App;

class TwitterInteractionsImageSource implements ImageSource
{
static string $prefix = "int";

public function getImageList(App $app, int $limit = 5, array $params = []): array
{
/** @var TwitterServiceProvider $twitter */
$twitter = $app->twitter;
$mutualsOnly = $params['mutuals'] ?? false;

return $this->getFeaturedByMentions($twitter, $limit, self::$prefix, $mutualsOnly);
}

public function getFeaturedByMentions(TwitterServiceProvider $twitter, $limit, $prefix = "int", $mutualsOnly = false): array
{
$response = $twitter->client->get('/statuses/mentions_timeline', [
'count' => 200,
'include_entities' => false
]);

$interactions = [];
$users = [];

//sort interactions by user id + count
foreach ($response as $tweet) {
$users[$tweet->user->id] = $tweet->user;
$int_count = $interactions[$tweet->user->id] ?? 0;
$interactions[$tweet->user->id] = $int_count+1;
}
arsort($interactions);

if ($mutualsOnly) {
$users_ids = array_keys($users);
$query_chunks = array_chunk($users_ids, 100);
$validation_queries = [];

foreach ($query_chunks as $chunk) {
$users_ids_list = implode(',', $chunk);
$validation_queries[] = $this->getRelationships($twitter, $users_ids_list);
}

$validated = [];
foreach ($validation_queries as $relationships) {
foreach ($relationships as $relationship) {
if (isset($relationship->connections) && (count($relationship->connections) > 1)) {
$validated[$relationship->id_str] = $users[$relationship->id_str];
}
}
}

$users = $validated;
}

$featured = [];
$count = 1;

//build an array with user info
foreach ($interactions as $user_id => $interaction) {
$user = $users[$user_id] ?? null;
if ($user) {
if (!$user->profile_image_url_https) {
continue;
}

$avatar_path = str_replace('normal', 'bigger', $user->profile_image_url_https);
$avatar = Storage::downloadImage($avatar_path);

if ($avatar) {
$featured[$prefix . "$count"] = [
'screen_name' => $user->screen_name,
'avatar' => $avatar,
'image_file' => $avatar
];

$count++;
}
}

if ($count > $limit) {
break;
}
}

return $featured;
}

public function getRelationships(TwitterServiceProvider $twitter, string $lookup)
{
return $twitter->client->get("friendships/lookup", [
"user_id" => $lookup
]);
}

}
Binary file added app/Resources/images/interactions.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 18 additions & 18 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 5dc67b4

Please sign in to comment.