Статьи

Создание с помощью API Twitter: повторение твитов из группы

Конечный продукт
Что вы будете создавать

Это руководство является частью серии статей, связанных с API Twitter. Вы можете найти оригинальное руководство по Birdcage в Твиттере здесь или перейти на страницу моего автора, чтобы быть в курсе последних дополнений к этой серии. Этот конкретный учебник основан на Birdcage, а также на моделях данных и коде из предыдущего твит-шторма .

Если вы такой же производитель, как я, вы часто используете Twitter, чтобы делиться информацией о своих творениях. Часто можно сказать больше, чем вы можете вместить в 140 символов, и большинство ваших подписчиков даже не видят каждый отдельный твит. Даже если они увидят то, о чем вы написали, они могут добавить это в избранное и забыть об этом. Полезно иметь службу, которая регулярно делится различными аспектами вашего объявления. Природа потока Twitter делает повторение полезным, в пределах разумного; переусердствовать это спам и раздражает.

Этот учебник основан на моей предыдущей статье о твиттере, чтобы показать вам, как создать сервис, который периодически публикует случайно выбранное обновление статуса вашей работы. Это автоматизирует задачу повторения и создания вариаций со временем, чтобы повысить вероятность того, что ваши подписчики в Твиттере будут взаимодействовать с вашим контентом.

Имейте в виду, что API Twitter имеет ограничения на повторяющийся контент. Вы добьетесь большего успеха, если предложите широкий спектр вариантов и запустите службу в учетной записи, которую вы также используете вручную для обмена другим контентом. Twitter, вероятно, отклонит повторяющиеся твиты чистых маркетинговых ботов, и вы, вероятно, столкнетесь с этим во время тестирования.

Основные требования к нашей функции следующие:

  • Позвольте пользователю написать и сохранить «группу» твитов внутри группы.
  • На регулярной основе случайным образом выбирайте один твит из группы, чтобы опубликовать его в нашей учетной записи.
  • Повторно размещайте эти элементы через настраиваемые пользователем интервалы со случайным временным сдвигом.
  • Разрешить пользователю устанавливать максимальное количество повторений, например, 100.
  • Позвольте пользователю сбросить группы, чтобы перезапустить повторение.

Создание инфраструктуры для групп, созданной нами в руководстве по твитам штормов, требует лишь небольшого количества дополнительного нового кода для повторяющихся твитов.

Мы будем использовать миграцию для создания таблицы групп, которая немного отличается от той, которая использовалась в учебнике по твит-шторму:

1
./app/protected/yiic migrate create create_group_table

Код ниже строит схему. Обратите внимание на отношение внешнего ключа, чтобы связать группу с определенной учетной записью Twitter:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php
 
class m141018_004954_create_group_table extends CDbMigration
{
   protected $MySqlOptions = ‘ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_unicode_ci’;
   public $tablePrefix;
   public $tableName;
 
   public function before() {
     $this->tablePrefix = Yii::app()->getDb()->tablePrefix;
     if ($this->tablePrefix <> »)
       $this->tableName = $this->tablePrefix.’group’;
   }
 
     public function safeUp()
    {
      $this->before();
    $this->createTable($this->tableName, array(
             ‘id’ => ‘pk’,
             ‘account_id’=>’integer default 0’,
             ‘name’=>’string default NULL’,
             ‘slug’=>’string default NULL’,
             ‘group_type’=>’tinyint default 0’,
             ‘stage’=>’integer default 0’,
             ‘created_at’ => ‘DATETIME NOT NULL DEFAULT 0’,
             ‘modified_at’ => ‘TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP’,
             ‘next_publish_time’=>’INTEGER DEFAULT 0’,
             ‘interval’=>’TINYINT DEFAULT 0’,
             ‘interval_random’=>’TINYINT DEFAULT 0’,
             ‘max_repeats’=>’INTEGER DEFAULT 0’,
             ‘status’=>’tinyint default 0’,
               ), $this->MySqlOptions);
               $this->addForeignKey(‘fk_group_account’, $this->tableName, ‘account_id’, $this->tablePrefix.’account’, ‘id’, ‘CASCADE’, ‘CASCADE’);
    }
 
    public function safeDown()
    {
        $this->before();
        $this->dropForeignKey(‘fk_group_account’, $this->tableName);
        $this->dropTable($this->tableName);
    }
}

Новые поля включают next_publish_time , interval , interval_random , max_repeats и status .

Мы также будем использовать ту же реляционную таблицу под названием GroupStatus которая отслеживает твиты статуса в каждой группе:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php
 
class m141018_020428_create_group_status_table extends CDbMigration
{
   protected $MySqlOptions = ‘ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_unicode_ci’;
   public $tablePrefix;
   public $tableName;
 
   public function before() {
     $this->tablePrefix = Yii::app()->getDb()->tablePrefix;
     if ($this->tablePrefix <> »)
       $this->tableName = $this->tablePrefix.’group_status’;
   }
 
     public function safeUp()
    {
      $this->before();
    $this->createTable($this->tableName, array(
             ‘id’ => ‘pk’,
             ‘group_id’ => ‘INTEGER NOT NULL’,
             ‘status_id’ => ‘INTEGER default 0’,
               ), $this->MySqlOptions);
               $this->addForeignKey(‘fk_group_status_group’, $this->tableName, ‘group_id’, $this->tablePrefix.’group’, ‘id’, ‘CASCADE’, ‘CASCADE’);
               $this->addForeignKey(‘fk_group_status_status’, $this->tableName, ‘status_id’, $this->tablePrefix.’status’, ‘id’, ‘CASCADE’, ‘CASCADE’);
    }
 
    public function safeDown()
    {
        $this->before();
        $this->dropForeignKey(‘fk_group_status_group’, $this->tableName);
        $this->dropForeignKey(‘fk_group_status_status’, $this->tableName);
        $this->dropTable($this->tableName);
    }
}

Используйте руководство по твит-шторму, чтобы узнать, как использовать Gii в Yii для создания кода скаффолдинга для контроллера группы, а также моделей для GroupStatus .

Вот как выглядит форма «Создать группу»:

Создать новую группу для повторяющихся твитов

Вот код представления формы. Обратите внимание, что есть JQuery, который показывает и скрывает дополнительные настройки, когда пользователь выбирает повторяющийся тип группы (в отличие от группы шторма твита):

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
<?php $form=$this->beginWidget(‘bootstrap.widgets.TbActiveForm’,array(
    ‘id’=>’group-form’,
    ‘enableAjaxValidation’=>false,
));
 
<?php
  if(Yii::app()->user->hasFlash(‘no_account’)
    ) {
  $this->widget(‘bootstrap.widgets.TbAlert’, array(
      ‘alerts’=>array( // configurations per alert type
        ‘no_account’=>array(‘block’=>true, ‘fade’=>true, ‘closeText’=>’×’),
      ),
  ));
}
?>
 
    <p class=»help-block»>Fields with <span class=»required»>*
 
    <?php echo $form->errorSummary($model);
     
    <?php
    echo CHtml::activeLabel($model,’account_id’,array(‘label’=>’Create group with which account:’));
    echo CHtml::activeDropDownList($model,’account_id’,Account::model()->getList(),array(’empty’=>’Select an Account’));
  ?>
   
    <?php echo $form->textFieldRow($model,’name’,array(‘class’=>’span5’,’maxlength’=>255));
 
    <?php
    echo CHtml::activeLabel($model,’group_type’,array(‘label’=>’Group Type:’));
  ?>
   
  <?php echo $form->dropDownList($model,’group_type’, $model->getTypeOptions());
 
 
     <div id =»section_schedule»>
     <p><strong>Schedule Post or Start Time:</strong><br />
     <em>Click the field below to set date and time</em></p>
     <?php
     $this->widget(
         ‘ext.jui.EJuiDateTimePicker’,
         array(
             ‘model’ => $model,
             ‘attribute’ => ‘next_publish_time’,
             ‘language’=> ‘en’,
             ‘mode’ => ‘datetime’, //’datetime’ or ‘time’ (‘datetime’ default)
             ‘options’ => array(
                 ‘dateFormat’ => ‘M d, yy’,
                 ‘timeFormat’ => ‘hh:mm tt’,//’hh:mm tt’ default
                 ‘alwaysSetTime’=> true,
             ),
         )
     );
     ?>
     </div> <!— end section schedule —>
 
     <div id =»section_recur»>
 
     <p><strong>Choose Options for Your Recurring Method (optional):</strong><br />
 
 
     <?php
         echo CHtml::activeLabel($model,’interval’,array(‘label’=>’Recurring: choose an interval:’));
         echo CHtml::activeDropDownList($model,’interval’,Status::model()->getIntervalList(false),array(’empty’=>’Select an interval’));
     ?>
 
     <?php
         echo CHtml::activeLabel($model,’max_repeats’,array(‘label’=>’Maximum number of repeated posts:’));
         echo CHtml::activeDropDownList($model,’max_repeats’,Status::model()->getMaxRepeatList(),array(’empty’=>’Select a maximum number’));
     ?>
 
     <?php
         echo CHtml::activeLabel($model,’interval_random’,array(‘label’=>’Choose a randomization period for your intervals:’));
         echo CHtml::activeDropDownList($model,’interval_random’,Status::model()->getRandomList(),array(’empty’=>’Select an interval’));
     ?>
 
   </div> <!— end recur —>
 
    <div class=»form-actions»>
        <?php $this->widget(‘bootstrap.widgets.TbButton’, array(
            ‘buttonType’=>’submit’,
            ‘type’=>’primary’,
            ‘label’=>$model->isNewRecord ?
        ));
    </div>
 
<?php $this->endWidget();
<script type=»text/javascript» charset=»utf-8″>
    $(document).ready(function()
    {
      $(‘#section_schedule’).hide();
      $(‘#section_method’).hide();
      $(«#Group_group_type»).change();
    });
    $(«#Group_group_type»).change(function () {
            var option = this.value;
            if (option ==0) {
              // tweet storm
              $(‘#section_schedule’).hide();
              $(‘#section_recur’).hide();
            } else if (option==10) {
              // recurring
              $(‘#section_schedule’).show();
              $(‘#section_recur’).show();
            }
        });
</script>

Вот как выглядит страница индекса контроллера группы после добавления нескольких групп:

Страница управления группами

Вот код, который запускает представление индекса. Во-первых, действие индекса контроллера группы:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
/**
    * Manages all models.
    */
   public function actionIndex()
   {
    
       $model=new Group(‘search’);
       $model->unsetAttributes();
       if(isset($_GET[‘Group’]))
           $model->attributes=$_GET[‘Group’];
 
       $this->render(‘admin’,array(
           ‘model’=>$model,
       ));
   }

Затем код представления администратора, который использует контроллер Yii Gridview:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<?php
$this->breadcrumbs=array(
    ‘Groups’=>array(‘index’),
    ‘Manage’,
);
 
$this->menu=array(
    array(‘label’=>’Add a Group’,’url’=>array(‘create’)),
);
 
Yii::app()->clientScript->registerScript(‘search’, «
$(‘.search-button’).click(function(){
    $(‘.search-form’).toggle();
    return false;
});
$(‘.search-form form’).submit(function(){
    $.fn.yiiGridView.update(‘group-grid’, {
        data: $(this).serialize()
    });
    return false;
});
«);
?>
 
<h1>Manage Groups of Tweets</h1>
 
<?php $this->widget(‘bootstrap.widgets.TbGridView’,array(
    ‘id’=>’group-grid’,
    ‘dataProvider’=>$model->search(),
    ‘filter’=>$model,
    ‘columns’=>array(
        array(
                  ‘header’=>’Account’,
                ‘name’=>’account_id’,
                ‘value’=>array(Account::model(),’renderAccount’),
            ),
        ‘name’,
        ‘slug’,
        array(
                  ‘header’=>’Type’,
                ‘name’=>’group_type’,
                ‘value’=>array(Group::model(),’renderGroupType’),
            ),
            array(
              ‘name’=>’status’,
                  ‘header’ => ‘Status’,
                   ‘value’ => array($model,’renderStatus’),
              ),
        ‘stage’,
    array(
          ‘htmlOptions’=>array(‘width’=>’150px’),
            ‘class’=>’bootstrap.widgets.TbButtonColumn’,
            ‘header’=>’Options’,
      ‘template’=>'{manage} {update} {delete}’,
          ‘buttons’=>array
          (
              ‘manage’ => array
              (
              ‘options’=>array(‘title’=>’Manage’),
                ‘label’=>'<i class=»icon-list icon-large» style=»margin:5px;»></i>’,
                ‘url’=>’Yii::app()->createUrl(«group/view», array(«id»=>$data->id))’,
              ),
          ),
        ), // end button array
    ),
));

Вы можете перейти на страницу просмотра группы, нажав на крайний левый значок «список» для любой отдельной группы. Оттуда вы можете добавить отдельные твиты:

Составление будущего твита для повторяющихся групп

Код составления практически идентичен коду в хранилище Birdcage. Как только вы добавите несколько твитов статуса в свою группу, это будет выглядеть примерно так:

Группа твитов для повторяющихся

Чем больше объем и разнообразие твитов вы можете предоставить, тем больше вероятность того, что вы сможете успешно репостировать автоматические твиты, не сталкиваясь со встроенными в Twitter ограничениями для ботов.

Повторяющиеся группы на самом деле не начинают твитить, пока вы не активируете их — обратите внимание на пункты меню справа на изображении выше. Повторяющиеся группы работают до тех пор, пока количество этапов не достигнет максимального количества опубликованных твитов. Пользователь также может сбросить группу, чтобы начать процесс заново.

Вот код для операций активации и сброса:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
public function activate($group_id) {
     // create an action to publish the storm in the background
     $gp = Group::model()->findByPK($group_id);
     if ($gp->status == self::STATUS_PENDING or $gp->status == self::STATUS_TERMINATED)
       $gp->status=self::STATUS_ACTIVE;
     else
       $gp->status=self::STATUS_TERMINATED;
     $gp->save();
   }
 
   public function reset($group_id) {
     // create an action to publish the storm in the background
     $gp = Group::model()->findByPK($group_id);
     if ($gp->status == self::STATUS_TERMINATED or $gp->status == self::STATUS_COMPLETE) {
       $gp->status=self::STATUS_ACTIVE;
       $gp->stage=0;
       $gp->next_publish_time=time()-60;
       $gp->save();
     }
   }

DaemonController индекса DaemonController вызывается cron в фоновом режиме. Это вызывает метод processRecurring модели processRecurring . Это выполняется для каждой учетной записи в поисках групп, которые просрочены.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public function processRecurring() {
    // loop through Birdhouse app users (usually 1)
    $users = User::model()->findAll();
    foreach ($users as $user) {
      $user_id = $user[‘id’];
      echo ‘User: ‘.$user[‘username’];lb();
      // loop through Twitter accounts (may be multiple)
      $accounts = Account::model()->findAllByAttributes(array(‘user_id’=>$user_id));
      foreach ($accounts as $account) {
        $account_id = $account[‘id’];
        echo ‘Account: ‘.$account[‘screen_name’];lb();
        $this->publishRecurring($account);
      } // end account loop
    } // end user loop
  }

Затем мы вызываем publishRecurring . Мы используем именованные области для поиска повторяющихся групп, для которых next_publish_time просрочено. Мы обрабатываем учетную запись, чтобы минимизировать количество необходимых соединений OAuth Twitter. Области применения показаны ниже.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public function publishRecurring($account) {
    // process any active, overdue groups
       $groups = Group::model()->in_account($account[‘id’])->recur()->active()->overdue()->findAll();
      if (count($groups)>0) {
        // make the connection to Twitter once for each account
         $twitter = Yii::app()->twitter->getTwitterTokened($account[‘oauth_token’], $account[‘oauth_token_secret’]);
         // process each overdue status
       foreach ($groups as $group) {
         // look at type
           // select a random status
           $status = Status::model()->in_specific_group($group->id)->find(array(‘order’=>’rand(‘.rand(1,255).’)’));
           echo $status->tweet_text;lb();
           // tweet it
           $tweet_id = Status::model()->postTweet($twitter,$status);
           // check maximum stage
           if ($group[‘stage’]>=$group[‘max_repeats’]) {
             $group[‘status’]=self::STATUS_COMPLETE;
             $group[‘next_publish_time’]=0;
           } else {
             // set next_publish time — it’s okay to use status model method
               $group[‘next_publish_time’]=Status::model()->getNextRecurrence($group);
             }
           $group[‘stage’]+=1;
           // save updated group data in db
           $updated_group = Group::model()->findByPk($group[‘id’]);
           $updated_group->stage = $group[‘stage’];
           $updated_group->next_publish_time = $group[‘next_publish_time’];
           $updated_group->status = $group[‘status’];
           $updated_group->save();
       } // end for loop of groups
      } // end if groups > 0
   }

Вот области ActiveRecord, которые мы используем для поиска просроченных групп:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public function scopes()
   {
       return array(
         ‘active’=>array(
             ‘condition’=>’status=’.self::STATUS_ACTIVE,
         ),
         ‘recur’=>array(
             ‘condition’=>’group_type=’.self::GROUP_TYPE_RECUR,
         ),
           ‘overdue’=>array(
             ‘condition’=>’next_publish_time < UNIX_TIMESTAMP(NOW())’,
           ),
       );
   }
    
   // custom scopes
   public function in_account($account_id=0)
   {
     $this->getDbCriteria()->mergeWith( array(
       ‘condition’=>’account_id=’.$account_id,
     ));
       return $this;
   }

Когда мы находим группу, которую необходимо обновить, мы используем этот код для случайного выбора твита статуса и обновления next_publish_time :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
// select a random status
           $status = Status::model()->in_specific_group($group->id)->find(array(‘order’=>’rand(‘.rand(1,255).’)’));
           echo $status->tweet_text;lb();
           // tweet it
           $tweet_id = Status::model()->postTweet($twitter,$status);
           // check maximum stage
           if ($group[‘stage’]>=$group[‘max_repeats’]) {
             $group[‘status’]=self::STATUS_COMPLETE;
             $group[‘next_publish_time’]=0;
           } else {
             // set next_publish time — it’s okay to use status model method
               $group[‘next_publish_time’]=Status::model()->getNextRecurrence($group);
             }
           $group[‘stage’]+=1;

Мы рассчитываем повторяемость, комбинируя интервал задержки и случайный сдвиг времени, который помогает твитам появляться в разных часовых поясах и немного разных периодах для достижения разных пользователей:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public function getNextRecurrence($status) {
     // calculates the next recurring time to post
     $start_time=time();
     if ($status[‘interval’] == self::STATUS_INTERVAL_HOUR) {
       $hours = 1;
     } else if ($status[‘interval’] == self::STATUS_INTERVAL_THREEHOUR) {
       $hours = 3;
     } else if ($status[‘interval’] == self::STATUS_INTERVAL_SIXHOUR) {
         $hours=6;
     } else if ($status[‘interval’] == self::STATUS_INTERVAL_HALFDAY) {
           $hours = 12;
       } else if ($status[‘interval’] == self::STATUS_INTERVAL_DAY) {
             $hours=24;
     } else if ($status[‘interval’] == self::STATUS_INTERVAL_TWODAY) {
           $hours = 48;
     } else if ($status[‘interval’] == self::STATUS_INTERVAL_THREEDAY) {
           $hours = 72;
     } else if ($status[‘interval’] == self::STATUS_INTERVAL_WEEK) {
             $hours = 168;
     }
     $start_time+=($hours*3600);
     $ri = $this->getRandomInterval($status[‘interval_random’]);
     if (($start_time+$ri)<time())
       $start_time-=$ri;
     else
       $start_time+=$ri;
     return $start_time;
   }
    
   public function getRandomInterval($setting) {
     // gets a random interval to differently space the recurring or repeating tweets
     $ri = 0;
     if ($setting == self::STATUS_RANDOM_HALFHOUR)
       $ri = 30;
     else if ($setting == self::STATUS_RANDOM_HOUR)
       $ri = 60;
     else if ($setting == self::STATUS_RANDOM_TWOHOUR)
       $ri = 120;
     else if ($setting == self::STATUS_RANDOM_THREEHOUR)
       $ri = 180;
     else if ($setting == self::STATUS_RANDOM_SIXHOUR)
       $ri = 360;
     else if ($setting == self::STATUS_RANDOM_HALFDAY)
       $ri = 720;
     else if ($setting == self::STATUS_RANDOM_DAY)
       $ri = 1440;
     // randomize the interval
     if ($ri>0) $ri = rand(1,$ri);
     $ri = $ri*60;
     if (rand(1,100)>50)
       $ri = 0 — $ri;
     return $ri;
   }

Опубликованные результаты со временем будут выглядеть примерно так:

Результаты повторяющихся групповых твитов

Вы можете выровнять распределение твитов, регистрируя используемую частоту и выбирая из наименее повторяющихся элементов для каждой итерации.

Возможно, вы также захотите разрешить группам иметь суффикс хэштегов, которые можно произвольно выбирать для изменения содержимого при твиттере.

Надеюсь, вы нашли это интересным и полезным. Опять же, вы можете найти оригинальное руководство по Birdcage в Twitter здесь или перейти на страницу моего автора, чтобы быть в курсе последних дополнений в серии API Twitter.

Пожалуйста, не стесняйтесь оставлять свои собственные исправления, вопросы и комментарии ниже. Мне особенно интересны ваши улучшения. Я стараюсь поддерживать связь с веткой обсуждения. Вы также можете связаться со мной в Twitter @reifman или написать мне напрямую.