PHPackages                             plin-code/laravel-instagram-digest - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. plin-code/laravel-instagram-digest

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

plin-code/laravel-instagram-digest
==================================

Laravel package that scrapes Instagram hashtags via Apify and sends a daily Telegram digest with inline action buttons.

v1.0.0(3mo ago)11[1 PRs](https://github.com/plin-code/laravel-instagram-digest/pulls)MITPHPPHP ^8.4CI passing

Since Apr 20Pushed 1mo agoCompare

[ Source](https://github.com/plin-code/laravel-instagram-digest)[ Packagist](https://packagist.org/packages/plin-code/laravel-instagram-digest)[ Docs](https://github.com/plin-code/laravel-instagram-digest)[ GitHub Sponsors](https://github.com/PlinCode)[ RSS](/packages/plin-code-laravel-instagram-digest/feed)WikiDiscussions main Synced 3w ago

READMEChangelog (1)Dependencies (13)Versions (3)Used By (0)

laravel-instagram-digest
========================

[](#laravel-instagram-digest)

[![Latest Version on Packagist](https://camo.githubusercontent.com/1c0b3daf8930328b2cbc7acd6a87aeacaa589560508320beb4f03a36788fdb4a/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f706c696e2d636f64652f6c61726176656c2d696e7374616772616d2d6469676573742e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/plin-code/laravel-instagram-digest)[![Total Downloads](https://camo.githubusercontent.com/08b9cee035f7162518e6d5ba3a9836d93cb80f6ae1348c4a8ceb262c64d67867/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f706c696e2d636f64652f6c61726176656c2d696e7374616772616d2d6469676573742e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/plin-code/laravel-instagram-digest)

Scrape Instagram hashtags via Apify, filter profiles by keywords and follower threshold, and send a daily Telegram digest with inline action buttons. Classify candidates with one tap.

What it does
------------

[](#what-it-does)

1. Runs the Apify `apify~instagram-scraper` actor against a list of hashtags.
2. Filters results by bio/username keyword match and a minimum follower count.
3. Upserts surviving profiles into `instagram_digest_profiles`.
4. Once a day, sends the next `N` pending profiles as Telegram cards with inline buttons: **Interesting**, **Reject**, **Show again later**. Custom actions pluggable.
5. Handles the callback when you tap a button: updates the profile status and removes the buttons from the message.

Bring your own data sources (hashtags, keywords, min-followers, chat id) via closures or plain config. Extend with custom action buttons and a custom card renderer.

Installation
------------

[](#installation)

```
composer require plin-code/laravel-instagram-digest
php artisan migrate
```

Add to your `.env`:

```
APIFY_TOKEN=your-apify-token
APIFY_ACTOR_ID=apify~instagram-scraper
APIFY_RESULTS_PER_HASHTAG=30

TELEGRAM_BOT_TOKEN=123:abc
TELEGRAM_CHAT_ID=-1001234567890
TELEGRAM_WEBHOOK_SECRET=a-long-random-string
```

Quickstart
----------

[](#quickstart)

In `AppServiceProvider@boot`:

```
use PlinCode\InstagramDigest\Facades\InstagramDigest;

public function boot(): void
{
    InstagramDigest::hashtagsUsing(fn () => ['trekking', 'hiking', 'guidealpine']);
    InstagramDigest::keywordsUsing(fn () => ['guida', 'trek', 'outdoor']);
    InstagramDigest::minFollowersUsing(fn () => 5000);
}
```

Register the Telegram webhook:

```
php artisan instagram-digest:webhook
```

Verify your Telegram setup end-to-end:

```
php artisan instagram-digest:demo
```

The demo uses a `placehold.co` URL for the placeholder image, so Telegram must be able to fetch that URL. If your network or bot configuration blocks external image fetches, pass a photo URL explicitly:

```
php artisan instagram-digest:demo --to=CHAT_ID
```

(Note: the `--to` option overrides the configured `chat_id` but currently uses the same placeholder image. For a full dry-run with your own image, register a custom `CardRenderer` — see below.)

Data sources: resolvers vs config
---------------------------------

[](#data-sources-resolvers-vs-config)

Every data source has two equivalent ways to supply it.

**Via config** (`config/instagram-digest.php` or env):

```
'hashtags' => ['trekking', 'hiking'],
'keywords' => ['guida', 'outdoor'],
'min_followers' => 5000,
```

**Via resolver closure** (takes precedence when registered):

```
InstagramDigest::hashtagsUsing(fn () => Hashtag::active()->pluck('name')->all());
InstagramDigest::keywordsUsing(fn () => Keyword::all()->pluck('term')->all());
InstagramDigest::minFollowersUsing(fn () => Setting::get('min_followers', 5000));
InstagramDigest::chatIdUsing(fn () => auth()->user()->telegram_chat_id);
InstagramDigest::dailyCountUsing(fn () => 10);
```

If no resolver is registered, the package falls back to config.

Custom actions
--------------

[](#custom-actions)

Register your own inline button:

```
use PlinCode\InstagramDigest\Facades\InstagramDigest;
use PlinCode\InstagramDigest\Models\Profile;

InstagramDigest::registerAction(
    key: 'archive',
    label: 'Archive',
    handler: fn (Profile $p) => $p->update(['status' => 'archived']),
);
```

Replace the default action set entirely:

```
InstagramDigest::defaultActions([
    new MyYesAction,
    new MyNoAction,
]);
```

Any class implementing `PlinCode\InstagramDigest\Contracts\DigestAction` is accepted.

Custom card rendering
---------------------

[](#custom-card-rendering)

**Option A: publish the Blade view and edit it**

```
php artisan vendor:publish --tag=instagram-digest-views
```

Then edit `resources/views/vendor/instagram-digest/card.blade.php`.

**Option B: register your own renderer**

```
use PlinCode\InstagramDigest\Contracts\CardRenderer;
use PlinCode\InstagramDigest\Facades\InstagramDigest;

InstagramDigest::renderCardUsing(MyCardRenderer::class);
```

Your renderer must return a `PlinCode\InstagramDigest\Support\CardPayload`.

Customizing the webhook route
-----------------------------

[](#customizing-the-webhook-route)

The webhook is registered by the package at `POST /instagram-digest/webhook/{secret?}` with the `api` middleware group. Both the URL prefix and the middleware stack are config-driven — edit `config/instagram-digest.php` after publishing:

```
php artisan vendor:publish --tag=instagram-digest-config
```

Then adjust:

```
'route' => [
    'prefix' => 'instagram-digest',           // appears in the URL: /{prefix}/webhook/{secret?}
    'middleware' => ['api'],                  // any middleware array — e.g. ['api', 'throttle:60,1']
],
```

If you need full control (different HTTP verb, route model binding, custom controller), you can bypass the auto-registered route by setting `'middleware' => ['api', 'should-never-match']` (breaks the route) and defining your own pointing at `PlinCode\InstagramDigest\Http\Controllers\WebhookController`.

Scheduling
----------

[](#scheduling)

The package does NOT register any scheduled tasks. Wire the commands yourself in `routes/console.php`:

```
use Illuminate\Support\Facades\Schedule;

Schedule::command('instagram-digest:scrape')->weekdays()->at('09:30');
Schedule::command('instagram-digest:send')->weekdays()->at('10:00');
```

Events
------

[](#events)

Listen to the following events to integrate with your own domain:

EventPayloadUse case`ProfileDiscovered``Profile $profile, bool $isNew`Sync to your CRM / lead model — `$isNew` distinguishes first-time discovery from refresh`ProfileStatusChanged``Profile $profile, string $from, string $to`React to user classification`DigestSent``array $profileIds`Metrics, auditing`ScrapingRunCompleted``Run $run`NotificationsExample listener:

```
public function handle(ProfileDiscovered $event): void
{
    if (! $event->isNew) {
        return;
    }

    Prospect::firstOrCreate(
        ['instagram_handle' => $event->profile->instagram_username],
        ['status' => 'new'],
    );
}
```

Testing your integration
------------------------

[](#testing-your-integration)

The package plays nicely with Laravel's HTTP fakes and event fakes. In your own tests:

```
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Event;
use PlinCode\InstagramDigest\Events\ProfileDiscovered;
use PlinCode\InstagramDigest\Jobs\RunHashtagScrapingJob;

it('my app reacts to ProfileDiscovered', function () {
    Event::fake([ProfileDiscovered::class]);
    Http::fake([
        'api.apify.com/*' => Http::response([/* ... */], 200),
    ]);

    dispatch_sync(new RunHashtagScrapingJob);

    Event::assertDispatched(ProfileDiscovered::class);
});
```

For the Telegram side, fake `api.telegram.org/*` and assert via `Http::assertSent(...)`.

Commands
--------

[](#commands)

CommandDescription`instagram-digest:scrape [--sync]`Dispatch the Apify scraping job.`instagram-digest:send [--count=N]`Dispatch the Telegram digest job.`instagram-digest:webhook [url?]`Register the Telegram webhook with Telegram.`instagram-digest:demo [--to=id]`Send one fake card to verify Telegram config.Testing
-------

[](#testing)

```
composer test
composer analyse
composer format
```

License
-------

[](#license)

MIT. See [LICENSE.md](LICENSE.md).

###  Health Score

40

—

FairBetter than 86% of packages

Maintenance88

Actively maintained with recent releases

Popularity3

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity52

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 97.6% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

95d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/7653cedfb706bdbaceab17cb57fa55d5e2744faeb722cfc1e2af8c3fd88f13ef?d=identicon)[danielebarbaro](/maintainers/danielebarbaro)

---

Top Contributors

[![danielebarbaro](https://avatars.githubusercontent.com/u/4376886?v=4)](https://github.com/danielebarbaro "danielebarbaro (41 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (1 commits)")

---

Tags

apifydaily-digestinstagramlaravellaravel-packagelead-generationoutreachphpphp8scrapingtelegramtelegram-botlaravelinstagramtelegramdigestscrapingapifyPlinCode

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/plin-code-laravel-instagram-digest/health.svg)

```
[![Health](https://phpackages.com/badges/plin-code-laravel-instagram-digest/health.svg)](https://phpackages.com/packages/plin-code-laravel-instagram-digest)
```

###  Alternatives

[nativephp/mobile

NativePHP for Mobile

1.1k75.1k106](/packages/nativephp-mobile)[codewithdennis/filament-select-tree

The multi-level select field enables you to make single selections from a predefined list of options that are organized into multiple levels or depths.

330530.5k30](/packages/codewithdennis-filament-select-tree)[simplestats-io/laravel-client

Server-side analytics for Laravel that follows the full funnel from visit to registration to payment, attributed to the channel that drove it. Revenue, MRR, churn and ad-spend profit (ROAS/CAC) per channel. GDPR compliant, ad-blocker proof.

5022.6k](/packages/simplestats-io-laravel-client)[rawilk/profile-filament-plugin

Profile &amp; MFA starter kit for filament.

3914.8k](/packages/rawilk-profile-filament-plugin)[plin-code/laravel-istat-geography

Laravel package for importing and managing Italian geography data from ISTAT

107.0k](/packages/plin-code-laravel-istat-geography)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
