-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathblogPoster.rb
executable file
·671 lines (544 loc) · 22.6 KB
/
blogPoster.rb
1
2
3
4
5
6
7
8
9
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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
#!/usr/bin/env ruby
# Blog Poster aims to make social and even regular blog posts
# easy to create. Just entering a URL will help you generate a full
# blog post that includes a file with title, body text and link
# that can be uploaded to the flat-file blogging system of
# your choice.
#
# This script is meant to send posts via FTP to a web server
# for use in an Ode blog, but it can probably be easily modified
# to create posts for other flat-file dynamic systems, such as
# Ode's ancestor Bloxsom.
# It could even be hacked to work with static-blogging systems
# such as Hugo or Jekyll, where you might want to do a straight
# file transfer to your local Documents directory and then trigger
# a build of your site.
#
# Automatic posting to Hugo blogs is on the roadmap.
#
# The other purpose of this script is to send your entry to
# social-media services Twitter and Mastodon.
#
# For Twitter, you need to open a developer account. The README
# goes over the procedure.
#
# For Mastodon, you need to get an access token for your
# account on your instance. It's marginally easier to do this
# than it is to get "permission" from Twitter, though both are
# very much doable.
#
# This script is written in the Ruby programming language.
# Aside from a few Ruby gems and their dependencies, you will
# need development tools to build the mastodon-api gem
# in Linux. See the README for details.
#
# The README doesn't have the Mastodon access token info yet,
# but it will soon.
#
# More information on Ode: http://ode.io
#
# Mastodon: https://joinmastodon.org/
#
# The Twitter Developer site: https://developer.twitter.com
#
# The author of this program is Steven Rosenberg ([email protected]).
#
# It is made available under [the MIT License](https://github.com/passthejoe/blogPoster/blob/master/README.md).
#
# Note the required gems for this program.
#
# The ones you'll have to install are:
#
# nokogiri
# net-sftp
# twitter
# net-ping
#
# and if you are running Windows:
# win32-security
#
require "nokogiri"
require "open-uri"
require "date"
require "net/sftp"
require "fileutils"
require "twitter"
require "net/ping"
def check_for_config
if !File.file?('blogPoster_configuration.rb')
FileUtils.cp 'blogPoster_configuration_example.rb', 'blogPoster_configuration.rb'
puts "\n\nEither this is your first time running blogPoster, or the configuration file is missing.\n"
puts "\nBefore you proceed, quit this program, open blogPoster_configuration.rb in a text editor and fill in your information.\n"
puts "\nSelect \'q\' to quit.\n"
else
require_relative "blogPoster_configuration.rb"
end
end
check_for_config
# Create the archive directory if it doesn't already exist
def check_for_archive
Dir.mkdir('archive') if !Dir.exist?('archive')
end
check_for_archive
# Load in keys/tokens for Twitter API
def load_twitter_keys
@client = Twitter::REST::Client.new do |config|
config.consumer_key = @twitter_consumer_key
config.consumer_secret = @twitter_consumer_secret
config.access_token = @twitter_access_token
config.access_token_secret = @twitter_access_token_secret
end
end
load_twitter_keys
def which_ruby
puts "\nYou are running Ruby #{RUBY_VERSION}"
end
def ruby_number
@our_ruby_number = RUBY_VERSION.to_f
end
# Display the menu, ask for user input and then execute based on
# that input
# Check for a live internet connection with net/ping
# This method prevents the script from crashing if there is
# no internet connection when uploading the post to a blog
# or other web site.
def are_we_connected?(host)
check = Net::Ping::External.new(host)
check.ping?
end
# Checking here for a connection -- might as well do that.
def is_computer_connected?
connected = are_we_connected?(@host_to_ping)
case connected
when true
puts "\nThis computer is connected to the internet"
else
puts "\nThis computer is NOT connected to the internet"
end
end
def welcome
puts "\nWelcome to blogPoster, the command-line program \nthat posts to \
your microblog, Twitter and Mastodon."
end
# Welcome messages
welcome
is_computer_connected?
which_ruby
ruby_number
def runmenu
# The menu is an array of strings with the first and last entries left "blank"
# to provide spacing
menu = ["",
"a - add a URL",
"b - new title",
"c - new text",
"s - text same as title",
"t - title same as text",
"d - URL with post?",
"e - display file",
"n - change url",
"f - save file",
"g - post file",
"h - edit title",
"i - edit text",
"p - is computer connected?",
"u - send to Twitter",
"r - raw post (no link)",
"l - list unarchived posts",
"m - send to Mastodon",
"x - archive posts",
"q - quit",
""
]
# Display the menu
puts menu
# Ask user to pick a task
puts "Choose a task\n\n"
yourTask = gets.downcase.chomp
puts "You chose #{yourTask}"
if yourTask == "a"
# Set booleans for including a URL and social directory
@urlBool = true
@socialDirectory = true
# Ask user for a URL
puts "Enter a URL\n\n"
@yourURL = gets.chomp
begin
# Use Nokogiri to open the Web page and get the title
targetPage = Nokogiri::HTML(URI.open(@yourURL))
@yourTitle = targetPage.css("title")[0].text.chomp
rescue
puts "Your URL didn't work\n"
else
@yourText = @yourTitle
# To name your post file, use the Date module
# to get the year in four digits, the month
# and day in two digits, then use the Time
# module to get the hours, minutes and seconds
# -- all as strings using strftime.
ourYear = Date.today.strftime("%Y")
ourMonth = Date.today.strftime("%m")
ourDate = Date.today.strftime("%d")
ourHour = Time.now.strftime("%H")
ourMinute = Time.now.strftime("%M")
ourSecond = Time.now.strftime("%S")
# Crunching the Web page title into the 2nd half of our file name
# Make it all lower case
fileNameWords = @yourTitle.downcase
# Substitute underscores for spaces
fileNameWords.gsub!(/\s/, "_")
# Remove all punctuation characters with \p{P}
# Remove all Math symbols, currency signs, dingbats, etc. with \p{S}
# Replace all with underscores
fileNameWords.gsub!(/[\p{P}\p{S}]/, "_")
# Remove doubled underscores
fileNameWords.gsub!(/_+/, "_")
# Get rid of leading underscore, we will get rid of trailing later
fileNameWords.gsub!(/^_/, "")
# Create the file name, not including the extension
@yourFileName = "#{ourYear}" + "_" + "#{ourMonth}" + "#{ourDate}" + "_" + "#{ourHour}" + "#{ourMinute}" + "#{ourSecond}" + "_#{fileNameWords}"
# Trim the full file name, not including the extension,
# to the length specified by @max_file_name_length
# in the configuration
@yourFileName = @yourFileName[0,@max_file_name_length]
# Remove trailing underscores
@yourFileName.gsub!(/_$/, "")
# Add the file name extension
@yourFileName = @yourFileName + @file_name_extension
# Print the output to the screen
puts "\n#{@yourTitle}"
puts "#{@yourText} <#{@yourURL}>\n\n"
puts "File name: #{@yourFileName}"
end
runmenu
elsif yourTask == "b"
# Ask user for a title
puts "Enter a title\n\n"
puts @yourTitle
@yourTitle = gets.chomp
puts @yourTitle
runmenu
elsif yourTask == "c"
# Ask user to enter body text
puts @yourText
puts "Enter body text\n\n"
@yourText = gets.chomp
if @urlBool == true
puts "\n#{@yourTitle}\n\n#{@yourText} <#{@yourURL}>\n"
else
puts "\n#{@yourTitle}\n\n#{@yourText}\n"
end
runmenu
elsif yourTask == "s"
# Make text the same as title
puts "Your old title: #{@yourTitle}"
puts "Your old text: #{@yourText}"
@yourText = @yourTitle.chomp
puts "\nYour old title: #{@yourTitle}"
puts "Your new text: #{@yourText}"
runmenu
elsif yourTask == "t"
# Make title the same as text
puts "Your old title: #{@yourTitle}"
puts "Your old text: #{@yourText}"
@yourTitle = @yourText
puts "\nYour new title: #{@yourTitle}"
puts "Your old text: #{@yourText}"
runmenu
elsif yourTask == "d"
# Ask user if they want a URL with the body
puts "Do you want a URL with this post?"
urlChoice = gets.chomp
if urlChoice == "y"
@urlBool = true
elsif urlChoice == "n"
@urlBool = false
else
puts "Please pick y or n"
urlChoice = gets.chomp
end
runmenu
elsif yourTask == "r"
# Raw post - no link plus the "now" directory
# Setting boolean variables for presence of a URL
# and use of an upload directory dedicated to
# social posts if your blog is set up that way.
@urlBool = false
@socialDirectory = false
puts "Social directory?"
socialChoice = gets.chomp
if socialChoice == "y"
@socialDirectory = true
end
# Ask user for title and text of the post
puts "Enter your title:"
@yourTitle = gets.chomp
puts "Enter your text:"
@yourText = gets.chomp
# To name your post file, use the Date module
# to get the year in four digits, the month
# and day in two digits, then use the Time
# module to get time in hours, minutes
# and seconds -- all as strings
# using strftime
ourYear = Date.today.strftime("%Y")
ourMonth = Date.today.strftime("%m")
ourDate = Date.today.strftime("%d")
ourHour = Time.now.strftime("%H")
ourMinute = Time.now.strftime("%M")
ourSecond = Time.now.strftime("%S")
# Use the given title to create a file name
# Make it all lower case
fileNameWords = @yourTitle.downcase
# Substitute underscores for spaces
fileNameWords.gsub!(/\s/, "_")
# Remove all punctuation characters with \p{P}
# Remove all Math symbols, currency signs, dingbats, etc. with \p{S}
# Replace all with underscores
fileNameWords.gsub!(/[\p{P}\p{S}]/, "_")
# Remove doubled underscores
fileNameWords.gsub!(/_+/, "_")
# Get rid of leading underscore, we will get rid of trailing later
fileNameWords.gsub!(/^_/, "")
# Print the output to the screen
puts fileNameWords
# Create the file name
@yourFileName = "#{ourYear}" + "_" + "#{ourMonth}" + "#{ourDate}" + "_" + "#{ourHour}" + "#{ourMinute}" + "#{ourSecond}" + "_#{fileNameWords}"
# Trim the full file name, not including the extension,
# to the length specified by @max_file_name_length
# in the configuration
@yourFileName = @yourFileName[0,@max_file_name_length]
# Remove trailing underscores
@yourFileName.gsub!(/_$/, "")
# Add the file name extension
@yourFileName = @yourFileName + @file_name_extension
# Print the output to the screen
puts "\n#{@yourTitle}"
puts "#{@yourText}\n\n"
puts "File name: #{@yourFileName}"
puts "Social Directory choice = " + socialChoice
puts "URL Bool = " + @urlBool.to_s
puts "Social Bool = " + @socialDirectory.to_s
runmenu
elsif yourTask == "e"
# Display the full post
puts "Here is your full post:\n\n"
puts @yourTitle
if @urlBool == true
puts "#{@yourText} <#{@yourURL}>"
else
puts @yourText
end
runmenu
elsif yourTask == "f"
# Make everything into a file
puts "I am saving your file so it can be uploaded to your site"
yourFile = File.new( @yourFileName, "w" )
yourFile.puts @yourTitle + "\n"
if @urlBool == true
yourFile.puts "#{@yourText} <#{@yourURL}>"
else
yourFile.puts @yourText
end
yourFile.close
puts "Your file has been saved"
#
# This file-rendering routine has been making the
# files un-deletable by Ruby when those files are
# created during the same session
#
# puts "This is your file:\n\n"
# File.open( $yourFileName ).each do |line|
# puts line
# end
runmenu
elsif yourTask == "g"
# send file on its way
# First run the are_we_connected? method to check
# for a live internet connection
connected = are_we_connected?(@host_to_ping)
# New sftp_upload method uses the Net::SFTP Gem
def sftp_upload
Net::SFTP.start(@your_ftp_domain, @your_ftp_login_name, :password => @your_ftp_password) do |sftp|
sftp.upload!(@yourFileName, @your_ftp_social_directory + "/" + @yourFileName)
end
end
# Now do the upload. An 'if/else' block only runs the upload
# if there is a live internet connection
if connected
sftp_upload
puts "Your file should now be on the server"
# If @ping_needed = true, ping the blog so the entry publishes
if @ping_needed
yourWebSiteToPing = @your_website_to_ping
puts "Plus I will ping the blog so this new entry publishes"
puts "Pinging now ..."
ping_it = URI.open(yourWebSiteToPing).read
puts "Pinged ... should be ready"
end
else
puts "You are not connected to the internet"
end
runmenu
elsif yourTask == 'h'
# Edit the title
# Turn the variable into file
# temp_title_for_editing is the Ruby variable
# temp_title is the actual file name
temp_title_for_editing = File.new("temp_title", "w+")
File.open("temp_title", "a+") { |file| file.write(@yourTitle) }
temp_title_for_editing.close
# File.chmod(0666, "temp_title")
puts "Edit your file in the editor of your choice"
system(@your_text_editor, 'temp_title')
temp_title_for_editing = File.open("temp_title", "r")
@yourTitle = temp_title_for_editing.read
temp_title_for_editing.close
puts "Your new title: #{@yourTitle}"
runmenu
elsif yourTask == 'i'
# Edit the text
# Turn the variable into file
temp_text_for_editing = File.new("temp_text", "w+")
# Write contents of @yourText into the temp_text file
File.open("temp_text", "a+") { |file| file.write(@yourText) }
temp_text_for_editing.close
# Open the file in your text editor
puts "Edit your file in the editor of your choice"
system(@your_text_editor, 'temp_text')
# Put contents of edited temp_text file
# into temp_text_for_editing variable
# and then putting that variable's contents into
# @yourText -- probably could skip a step and write
# directly to @yourText
temp_text_for_editing = File.open("temp_text", "r")
@yourText = temp_text_for_editing.read.chomp
temp_text_for_editing.close
puts "Your new text: #{@yourText}"
runmenu
elsif yourTask == 'p'
is_computer_connected?
runmenu
elsif yourTask == 'n'
# Change the URL for the current post
@urlBool = true
# Ask user for a URL
puts "Enter a new URL\n\n"
@yourURL = gets.chomp
runmenu
elsif yourTask == 'x'
# Archive all the .txt posts
# (Thanks to https://www.ruby-forum.com/topic/4041707#1055891 for the tip)
# for :force => true -- http://ruby-doc.org/stdlib-2.2.3/libdoc/fileutils/rdoc/FileUtils.html#method-c-mv
Dir.glob("*.txt") {|f| FileUtils.move File.expand_path(f), "archive", :force => true }
puts "Your posts have been archived in the \"archive\" directory"
# Return to the menu
runmenu
elsif yourTask == 'l'
# List "active" files
active_files = Dir.glob("*.txt")
puts active_files
# Return to the menu
runmenu
elsif yourTask == 'u'
# Send to Twitter
puts "Sending this post to Twitter"
puts "First checking the length ...\nTweets cannot exceed #{@twitter_max_length} characters,\nincluding the URL ...\n"
checking_length = @yourText + @yourURL if @urlBool
checking_length = @yourText if !@urlBool
puts "Your post length is #{checking_length.length} characters"
length_ok = true if checking_length.length <= @twitter_max_length
begin
if @urlBool && length_ok
puts "Your post is not too long ..."
@yourText = @yourText.chomp
@client.update("#{@yourText} #{@yourURL}")
puts "Post sent to Twitter"
elsif length_ok
puts "Your post is not too long ..."
@yourText = @yourText.chomp
@client.update("#{@yourText}")
puts "Post sent to Twitter"
else
puts "Please shorten your text length to
#{@twitter_max_length} characters, including any
URL that is included.
Click 'i' to edit your text"
end
rescue
puts "\nSomething happened with Twitter"
puts "Your tweet did not go through"
else
puts "Success!"
end
# Return to the menu
runmenu
#
# Mastodon posting
#
# As of 8/8/2021, Mastodon posting is handled by the http gem instead of
# the mastodon-api gem, which hasn't been maintained in more than a year
# and hasn't worked well with Ruby since version 2.7. It also conflicts
# with the twitter gem v.7.
#
# Luckily it is easy to post to Mastodon with regular web calls,
# and it can all be done with the http gem.
#
# In the configuration file, @mastodon_base_url is the URL of your Mastodon instance
# @mastodon_bearer_token is the token you need to access your Mastodon account
# on your instance.
#
# You can get this token at:
# Access Token Generator for Mastodon API
# https://takahashim.github.io/mastodon-access-token/
# Mastodon URL is your instance's URL
# Client Name is your app name (e.g. blogPoster)
# Web site is where you want the client name to link on your live post
# for Scopes, pick Read Writer
# The access_token this web site generates is your @mastodon_bearer_token
#
elsif yourTask == 'm'
# Send to Mastodon
puts "Sending this post to your Mastodon instance"
puts "First checking the length ...\nToots cannot exceed #{@mastodon_max_length} characters,\nincluding the URL ...\n"
checking_length = @yourText + @yourURL if @urlBool
checking_length = @yourText if !@urlBool
puts "Your post length is #{checking_length.length} characters"
length_ok = true if checking_length.length <= @mastodon_max_length
begin
if @urlBool && length_ok
puts "Your post is not too long ..."
@yourText = @yourText.chomp
HTTP.auth("Bearer " + @mastodon_bearer_token)
.post(@mastodon_base_url + "/api/v1/statuses", :params => {:status => @yourText + " " + @yourURL})
puts "Post sent to Mastodon"
elsif length_ok
puts "Your post is not too long ..."
@yourText = @yourText.chomp
HTTP.auth("Bearer " + @mastodon_bearer_token)
.post(@mastodon_base_url + "/api/v1/statuses", :params => {:status => @yourText})
puts "Post sent to Mastodon"
else
puts "Please shorten your text length to
#{@mastodon_max_length} characters, including any
URL that is included.
Click 'i' to edit your text"
end
rescue
puts "\nSomething happened with Mastodon"
puts "Your toot did not go through"
else
puts "Success!"
end
# Return to the menu
runmenu
elsif yourTask == 'q'
puts "Goodbye ..."
exit
else
puts "Your choice isn't in the list above, so enter it again"
runmenu
end
end
# Return to the menu
runmenu