Skip to content

Commit

Permalink
Merge pull request leouofa#114 from realstorypro/add-instapins
Browse files Browse the repository at this point in the history
Adding InstaPins Functionality
  • Loading branch information
Leonid Medovyy authored Jul 31, 2023
2 parents 48dcd15 + 0a158ba commit 075837f
Show file tree
Hide file tree
Showing 26 changed files with 487 additions and 16 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ruby_on_rails.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:

services:
postgres:
image: postgres:11-alpine
image: postgres:15-alpine
ports:
- "5432:5432"
env:
Expand Down
68 changes: 68 additions & 0 deletions app/controllers/instapins_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
class InstapinsController < ApplicationController
include RestrictedAccess
def index
scope = set_scope

@instapins = Instapin.send(scope).order(id: :desc).page params[:page]
@total_instapins = Instapin.send(scope).count
end

def show
@instapin = Instapin.find(params[:id])
end

def edit
@instapin = Instapin.find(params[:id])
@instapin_text = JSON.parse(@instapin.stem)["post"]
end

def update
@instapin = Instapin.find(params[:id])
# Parse the JSON stored in the stem attribute
stem_json = JSON.parse(@instapin.stem)

# Update the "instapin" value in the parsed JSON object
stem_json["post"] = params[:instapin][:stem]

# Convert the JSON object back to a string and save it to the instapin's stem attribute
@instapin.stem = stem_json.to_json

# Turn off invalid JSON since its now valid
@instapin.invalid_json = false

# Save the InstaPin
if @instapin.save
redirect_to instapin_path(@instapin), notice: 'InstaPin was successfully updated.'
else
render :edit
end
end

def approve
@instapin = Instapin.find(params[:id])
@instapin.update(approved: true)
redirect_to instapin_path(@instapin)
end

def disapprove
@instapin = Instapin.find(params[:id])
@instapin.update(approved: false)
redirect_to instapin_path(@instapin)
end

private

def set_scope
if params[:scope] && params[:scope] == 'pending'
return 'needs_approval'
elsif params[:scope] && params[:scope] == 'approved'
return 'approved_instapins'
elsif params[:scope] && params[:scope] == 'denied'
return 'denied'
elsif params[:scope] && params[:scope] == 'published'
return 'published'
end

'all'
end
end
3 changes: 3 additions & 0 deletions app/jobs/assemble_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ def perform(*_args)

# [x] Create Tweets from Discussions
Tweets::ProcessTwitterStemsJob.perform_now

# [x] Create Instapins from Uploaded Discussions
Instapins::ProcessInstapinStemsJob.perform_now
ensure
# Unlock the job when finished
lock.update(locked: false)
Expand Down
80 changes: 80 additions & 0 deletions app/jobs/instapins/make_stem_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
class Instapins::MakeStemJob < ApplicationJob
queue_as :default
include SettingsHelper

def perform(discussion:)
return if discussion.instapin.present?

@client = OpenAI::Client.new

prompts = discussion.story.sub_topic.prompts
story = discussion.story

system_role = <<~SYSTEM_ROLE
#{s("prompts.#{prompts}.tweets.stem.system_role")}
SYSTEM_ROLE

brief = <<~BRIEF
You have received the following story in JSON format:
#{discussion.stem}
BRIEF

question = <<~QUESTION
- You have received the following as a news story about `#{story.sub_topic.name}` and `#{story.tag.name}`.
- It is your job to write #{s("prompts.#{prompts}.tweets.stem.tone")} 400 character `post` about this story.
- You must include relevant 2 to 3 `hashtags` and 2 to 3 `emojis` into the `post`.
- The return result MUST be in JSON format in the following structure:
```
{
post: "this is a post",
}
```
QUESTION

messages = [
{ role: "system", content: system_role },
{ role: "user", content: brief },
{ role: "user", content: question }
]

invalid_json = true
counter = 0
response = nil

while invalid_json && counter < 6
response = chat(messages:)

counter += 1

break if response["error"].present?

invalid_json = false if valid_json?(response["choices"][0]["message"]["content"])
Rails.logger.debug "invalid_json: #{invalid_json} | counter: #{counter}"
end

if response["error"].present?
Instapin.create(discussion:, processed: true, invalid_json:)
else
Instapin.create(discussion:, stem: response["choices"][0]["message"]["content"], processed: true, invalid_json:)
end
end

def valid_json?(json_string)
JSON.parse(json_string)
true
rescue JSON::ParserError
false
end

def chat(messages:)
@client.chat(
parameters: {
model: ENV['OPENAI_GPT_MODEL'], # Required.
messages:,
temperature: 0.7
}
)
end
end
9 changes: 9 additions & 0 deletions app/jobs/instapins/process_instapin_stems_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class Instapins::ProcessInstapinStemsJob < ApplicationJob
queue_as :default

def perform(*args)
Discussion.ready_to_create_instapins.each do |discussion|
Instapins::MakeStemJob.perform_now(discussion:)
end
end
end
87 changes: 87 additions & 0 deletions app/jobs/instapins/publish_instapin_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
class Instapins::PublishInstapinJob < ApplicationJob
queue_as :default

def perform(discussion:)
return if discussion.instapin.uploaded

story = discussion.story
instapin = discussion.instapin

platforms = []
platforms.push 'pinterest' if ENV['PINTEREST_ENABLED']
platforms.push 'instagram' if ENV['INSTAGRAM_ENABLED']
platforms.push 'facebook' if ENV['FACEBOOK_ENABLED']

instapin_text = JSON.parse(instapin.stem)['post']

if story.sub_topic.ai_disclaimer
instapin_text = "📟 AI Perspective: #{instapin_text}"
end

max_characters = 400

# Truncate truncated_instapin_text to fit within the max_characters limit
# 31 characters are reserved for URL and a space
truncated_instapin_text = instapin_text.truncate(max_characters - 31, omission: '...')

landscape_images = discussion.story.imaginations.where(aspect_ratio: :landscape).sample(3)

landscape_image_urls = landscape_images.map do |landscape_image|
"https://ucarecdn.com/#{landscape_image.uploadcare.last['uuid']}/-/format/auto/-/quality/smart/-/resize/1440x/"
end


story_pro_discussion = StoryPro.get_discussion(discussion.story_pro_id)
discussion_slug = story_pro_discussion["entry"]["slug"]
category_slug = get_story_pro_category_slug(story)

discussion_url = "#{ENV['STORYPRO_URL']}/discussions/#{category_slug}/#{discussion_slug}"

full_instapin = "#{truncated_instapin_text} \n\n Read full story at: #{discussion_url}"

carousel_items = landscape_image_urls.map do |landscape_url|
{
name: discussion.parsed_stem["title"].truncate(50),
link: discussion_url,
picture: landscape_url
}
end

return if platforms.blank?


if platforms.include? 'instagram'
Ayrshare.post_message(post: full_instapin, platforms: ['instagram'] , media_urls: landscape_image_urls)
end

if platforms.include? 'pinterest'
Ayrshare.post_pinterest_message(post: truncated_instapin_text,
platforms: ['pinterest'] ,
media_urls: landscape_image_urls.sample(1),
pinterest_options: {
title: discussion.parsed_stem["title"].truncate(100),
link: discussion_url,
boardId: story.sub_topic.pinterest_board
})
end

# if platforms.include? 'facebook'
# Ayrshare.post_carousel(post: truncated_instapin_text,
# facebook_options: {
# carousel: {
# link: discussion_url,
# items: carousel_items
# }
# })
# end


instapin.update(uploaded: true, published_at: Time.now.utc)
end

def get_story_pro_category_slug(story)
story_pro_categories = StoryPro.get_categories
found_story_pro_category = story_pro_categories.find { |c| c["id"] == story.sub_topic.storypro_category_id }
found_story_pro_category['slug']
end
end
13 changes: 13 additions & 0 deletions app/jobs/instapins/publish_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class Instapins::PublishJob < ApplicationJob
queue_as :default
MAX_INSTAPINS_PUBLISHED_AT_A_TIME = 1

def perform(*args)
settings = Setting.instance
return unless settings.within_publish_window?

Discussion.ready_to_upload_instapins.sample(MAX_INSTAPINS_PUBLISHED_AT_A_TIME).each do |discussion|
Instapins::PublishInstapinJob.perform_now(discussion:)
end
end
end
9 changes: 3 additions & 6 deletions app/jobs/tweets/publish_tweet_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ def perform(discussion:)
platforms = []
platforms.push 'twitter' if ENV['TWITTER_ENABLED']
platforms.push 'linkedin' if ENV['LINKEDIN_ENABLED']
platforms.push 'pinterest' if ENV['PINTEREST_ENABLED']
platforms.push 'facebook' if ENV['FACEBOOK_ENABLED']

tweet_text = JSON.parse(tweet.stem)['tweet']
Expand All @@ -20,10 +19,9 @@ def perform(discussion:)
end

max_characters = 280
auto_hashtag = false

# Truncate tweet_text to fit within the MAX_CHARACTERS limit
# 28 characters are reserved for URL and a space
# Truncate tweet_text to fit within the max_characters limit
# 31 characters are reserved for URL and a space
truncated_tweet_text = tweet_text.truncate(max_characters - 31, omission: '...')

card_image = tweet.discussion.story.imaginations.where(aspect_ratio: :card).sample(1)
Expand All @@ -39,8 +37,7 @@ def perform(discussion:)

return if platforms.blank?

# Ayrshare.post_message(post: full_tweet, platforms: , media_urls: [card_image_url], auto_hashtag:)
Ayrshare.post_plain_message(post: full_tweet, platforms:, auto_hashtag:)
Ayrshare.post_plain_message(post: full_tweet, platforms:)
tweet.update(uploaded: true, published_at: Time.now.utc)
end

Expand Down
1 change: 1 addition & 0 deletions app/jobs/update_settings_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def perform(*args)
max_stories_per_day: sub_topic["max_stories_per_day"],
storypro_category_id: sub_topic["storypro_category_id"],
storypro_user_id: sub_topic["storypro_user_id"],
pinterest_board: sub_topic["pinterest_board"],
ai_disclaimer: sub_topic["ai_disclaimer"],
prompts: sub_topic["prompts"],
active: true)
Expand Down
32 changes: 28 additions & 4 deletions app/lib/ayrshare.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ def self.configure
yield(configuration)
end

def self.post_plain_message(post:, platforms:, auto_hashtag:)
def self.post_plain_message(post:, platforms:)
url = "https://app.ayrshare.com/api/post"
headers = { 'Authorization' => "Bearer #{configuration.api_key}" }
body = { post:, platforms:, autoHashtag: auto_hashtag }
body = { post:, platforms: }

response = HTTParty.post(url, headers:, body:)

Expand All @@ -31,10 +31,34 @@ def self.post_plain_message(post:, platforms:, auto_hashtag:)
JSON.parse(response.body)
end

def self.post_message(post:, platforms:, auto_hashtag:, media_urls: [])
def self.post_pinterest_message(post:, platforms: ['pinterest'], media_urls: [], pinterest_options: {})
url = "https://app.ayrshare.com/api/post"
headers = { 'Authorization' => "Bearer #{configuration.api_key}" }
body = { post:, platforms:, mediaUrls: [media_urls], autoHashtag: auto_hashtag }
body = { post:, platforms:, mediaUrls: [media_urls], pinterestOptions: pinterest_options }

response = HTTParty.post(url, headers:, body:)

raise "Error: #{response.code} - #{response.body}" unless response.code == 200

JSON.parse(response.body)
end

def self.post_carousel(post:, facebook_options: {})
url = "https://app.ayrshare.com/api/post"
headers = { 'Authorization' => "Bearer #{configuration.api_key}" }
body = { post:, platforms: ['facebook'], faceBookOptions: facebook_options }

response = HTTParty.post(url, headers:, body:)

raise "Error: #{response.code} - #{response.body}" unless response.code == 200

JSON.parse(response.body)
end

def self.post_message(post:, platforms:, media_urls: [])
url = "https://app.ayrshare.com/api/post"
headers = { 'Authorization' => "Bearer #{configuration.api_key}" }
body = { post:, platforms:, mediaUrls: [media_urls] }

response = HTTParty.post(url, headers:, body:)

Expand Down
Loading

0 comments on commit 075837f

Please sign in to comment.