Spanish (Español) translation by Elías Nicolás (you can also view the original English article)

Este tutorial forma parte de una serie relacionada con la API de Twitter. Puede encontrar el tutorial original de Birdcage Twitter aquí o seguir mi página de autor para mantenerse al día con las últimas adiciones a la serie. Este tutorial en particular se basa en Birdcage y los modelos de datos y el código de la anterior tutorial tweet storm.
Si es un creador como yo, y a menudo utiliza Twitter para compartir detalles sobre sus creaciones. Y tiene más que decir de lo que puede caber en 140 caracteres, y la mayoría de sus seguidores ni siquiera ven cada tweet individual. Incluso si ven algo que has publicado, que podrían darle favorito y olvidarlo. Es útil tener un servicio que comparte regularmente diferentes aspectos de un anuncio suyo. La naturaleza del Twitter Stream hace que la repetición sea útil, dentro de la razón; exagerar es spam y molesto.
Este tutorial se basa en mi anterior artículo de tweet storm para mostrarle cómo crear un servicio que publica una actualización de estado seleccionada al azar sobre su trabajo de forma recurrente. Esto automatiza la tarea de repetir y crear variaciones en el tiempo para aumentar la probabilidad de que sus seguidores de Twitter se involucren con su contenido.
Tenga en cuenta que la API de Twitter tiene límites en contenido repetitivo. Tendrá más éxito si ofrece una amplia variedad de variaciones y ejecuta el servicio en una cuenta que también se utiliza manualmente para compartir otro contenido. Twitter probablemente rechazará los tweets repetitivos de los bots de marketing puros—y es probable que se encuentre con esto durante las pruebas.
Requisitos de la característica
Los requisitos básicos para nuestra característica son los siguientes:
- Permitir que el usuario escriba y almacene un "montón" de tweets dentro de un grupo.
- De manera recurrente, seleccionar aleatoriamente un tweet del grupo para publicarlo en nuestra cuenta.
- Publique repetidamente estos artículos en intervalos configurables por el usuario con un cambio de tiempo aleatorio.
- Permitir al usuario establecer un número máximo de recurrencias, por ejemplo: 100.
- Permitir al usuario reiniciar grupos para reiniciar la repetición.
Basándose en la infraestructura para los grupos que hemos creado en el tutorial tweet storm, sólo se requiere una modesta cantidad de código nuevo adicional para tweets recurrentes.
El modelo de la base de datos
Usaremos la migración para crear la tabla Group, que es ligeramente diferente de la utilizada en el tutorial de tweet storm:
./app/protected/yiic migrate create create_group_table
El código siguiente crea el esquema. Tenga en cuenta la relación de clave externa para vincular el grupo a una cuenta de Twitter específica:
<?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); } }
Los nuevos campos incluyen next_publish_time
, interval
, interval_random
, max_repeats
y status
.
También usaremos la misma tabla relacional llamada GroupStatus
que rastrea los tweets de estado dentro de cada grupo:
<?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); } }
Creando el Código
Utilice el tutorial tweet storm para ver cómo usar Yi's Gii para crear el código para el controlador de grupo, así como los modelos para GroupStatus
.
A continuación, se muestra el aspecto del formulario "Crear un grupo":

Aquí está el código de vista para el formulario. Observe que hay jQuery que muestra y oculta los ajustes adicionales cuando el usuario selecciona un tipo de grupo recurrente (en contraposición a un grupo de tweet storm):
<?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">*</span> are required.</p> <?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 ? 'Create' : 'Save', )); ?> </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>
Esto es lo que parece la página del controlador índice de Group una vez que ha agregado algunos grupos:

Este es el código que ejecuta la vista de índice. En primer lugar, la acción del índice del controlador Group:
/** * Manages all models. */ public function actionIndex() { $model=new Group('search'); $model->unsetAttributes(); // clear any default values if(isset($_GET['Group'])) $model->attributes=$_GET['Group']; $this->render('admin',array( 'model'=>$model, )); }
A continuación, el código de vista de administrador, que utiliza el controlador Yii Gridview:
<?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 ), )); ?>
Puede acceder a la página de vista de grupo haciendo clic en el ícono de "lista" más a la izquierda para cualquier grupo individual. A partir de ahí, puede agregar tweets individuales:

El código para la composición es casi idéntico al del repositorio Birdcage. Una vez que haya añadido un puñado de tweets a su grupo, se verá algo como esto:

Cuanto más volumen y variedad de tweets pueda proporcionar, más probable será que pueda volver a publicar tweets automatizados sin tener que encontrar las limitaciones internas de Twitter en bots.
Los grupos recurrentes en realidad no comienzan a twittear hasta que los activen—observe las opciones del menú del lado derecho en la imagen anterior. Los grupos recurrentes se ejecutan hasta que el número de etapas alcanza el número máximo de tweets publicados. El usuario también puede restablecer un grupo para iniciar el proceso.
Aquí está el código para las operaciones de activación y restablecimiento:
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; // reset stage to zero $gp->next_publish_time=time()-60; // reset to a minute ago $gp->save(); } }
Publicar tweets recurrentes
El método índice de DaemonController
es llamado por cron. Este llama al método processRecurring
del modelo Group. Esto se ejecuta a través de cada cuenta buscando grupos que están atrasados.
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 }
Entonces, llamamos publishRecurring
. Utilizamos los ámbitos nombrados para buscar grupos recurrentes cuya next_publish_time
está atrasada. Procesamos por cuenta para minimizar el número de conexiones de Twitter de OAuth necesarias. Los alcances se muestran más adelante.
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 }
Aquí están los scopes de ActiveRecord que utilizamos para encontrar los grupos atrasados:
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; }
Cuando
encontramos un grupo que necesita ser actualizado, usamos este código
para seleccionar aleatoriamente un tweet y actualizar el
next_publish_time
:
// 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;
Calculamos la recurrencia combinando el intervalo de retardo y un desplazamiento de tiempo aleatorio que ayuda a que los tweets aparezcan en diferentes zonas horarias y períodos ligeramente diferentes para llegar a diferentes usuarios:
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; // if time before now, reverse it 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; // times # of seconds if (rand(1,100)>50) $ri = 0 - $ri; return $ri; }
Los resultados publicados con el tiempo se verán así:

Mejoras futuras
Es posible que desee igualar la distribución de los tweets registrando la frecuencia de cada uno se utiliza y elegir entre los elementos menos repetidos con cada iteración.
También puede permitir que los grupos tengan un sufijo de hashtags que pueden seleccionarse aleatoriamente para variar el contenido cuando publica los tweets.
Para concluir
Espero que hayas encontrado esto interesante y útil. Una
vez más, puede encontrar el tutorial original de Birdcage Twitter aquí o
seguir mi página de autor para mantenerse al día con las últimas
publicaciones de la serie de la API de Twitter.
Por favor, siéntase libre de publicar sus propias correcciones, preguntas y comentarios a continuación. Estoy especialmente interesado en sus mejoras. Intento mantenerme comprometido con el hilo de discusión. También puede contactarme en Twitter @reifman o enviarme un correo electrónico directamente.
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Update me weeklyEnvato Tuts+ tutorials are translated into other languages by our community members—you can be involved too!
Translate this post