tblog_duopack 0.0.37 → 0.0.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/tblog_duopack.rb +461 -264
- metadata +3 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bb08b4c28392011498f8a6683f49c393d6a5fe70d83839f5185719143c1954d1
|
4
|
+
data.tar.gz: ab184f85d87dbea4323e2c4c8a2ac558ddd6d3dce2cf22fd476e737c276a43cc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 86af3d2f53bb272ae1be71a518aa8df37b62fd9ba9b344c1f308d3b64aa5fe0257e09cc7cf1836e5d95e03edf7ad79830467c65b2c0495a46814cb6378e09397
|
7
|
+
data.tar.gz: 3469d71fd2b555ec39f895870e3c68c1f245c274058e86b957a9845b7a91cdf702b2db96cf1133c81ccd09f34eb58102b4a0a792521e828fc123ffe76dda25f2
|
data/lib/tblog_duopack.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'glimmer-dsl-libui'
|
2
2
|
require 'selenium-webdriver'
|
3
|
-
# require 'webdrivers'
|
4
3
|
require 'iconv'
|
5
4
|
require 'nokogiri'
|
6
5
|
require 'http'
|
@@ -14,9 +13,9 @@ require 'clipboard'
|
|
14
13
|
require 'crack'
|
15
14
|
require 'uri'
|
16
15
|
require 'cgi'
|
17
|
-
require 'digest'
|
18
16
|
require 'auto_click'
|
19
17
|
require 'rainbow/refinement'
|
18
|
+
require 'httpclient'
|
20
19
|
include AutoClickMethods
|
21
20
|
using Rainbow
|
22
21
|
include Glimmer
|
@@ -248,115 +247,186 @@ class Naver
|
|
248
247
|
end
|
249
248
|
|
250
249
|
def chrome_start(proxy)
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
250
|
+
# 공통 옵션 설정
|
251
|
+
begin
|
252
|
+
Selenium::WebDriver::Chrome::Service.driver_path = './chromedriver.exe'
|
253
|
+
rescue => e
|
254
|
+
puts "chromedriver 버전 불일치!!"
|
255
|
+
puts "아래 지침을 따라주세요."
|
256
|
+
puts "1.프로그램 종료!"
|
257
|
+
puts "2.크롬 업데이트!"
|
258
|
+
puts "3.프로그램 폴더 내부에 ★tip★-시작시-크롬창이....파일 실행"
|
259
|
+
puts "4.안내된 방식으로 크롬 드라이버 교체"
|
260
|
+
puts "5.재 시작"
|
261
|
+
exit 1
|
262
|
+
end
|
263
|
+
options = Selenium::WebDriver::Chrome::Options.new
|
264
|
+
options.add_argument('--no-first-run') # 자동 실행 시 나타나는 "첫 실행" 화면 방지
|
265
|
+
options.add_extension('./crx/app.crx') # 확장 프로그램을 첫 번째 탭에 추가
|
266
|
+
options.add_argument('--disable-blink-features=AutomationControlled')
|
267
|
+
options.add_argument('--disable-popup-blocking')
|
268
|
+
options.add_argument('--dns-prefetch-disable')
|
269
|
+
options.add_argument('--disable-dev-shm-usage')
|
270
|
+
options.add_argument('--disable-software-rasterizer')
|
271
|
+
options.add_argument('--ignore-certificate-errors')
|
272
|
+
options.add_argument('--disable-gpu') # GPU 가속 끄기
|
273
|
+
options.add_argument('user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36') # user-agent 위조
|
274
|
+
options.add_argument('--disable-web-security')
|
275
|
+
options.add_argument('--allow-running-insecure-content')
|
276
|
+
options.add_argument('--allow-insecure-localhost')
|
277
|
+
options.add_argument('--no-sandbox')
|
278
|
+
options.add_argument('--disable-translate')
|
279
|
+
options.add_argument('--disable-extensions-file-access-check')
|
280
|
+
options.add_argument('--disable-impl-side-painting')
|
281
|
+
options.add_argument('--log-level=3')
|
282
|
+
# 자동화된 테스트 제거
|
283
|
+
options.exclude_switches = ['enable-automation']
|
271
284
|
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
options.add_preference("profile.default_content_setting_values.notifications", 2) # 알림 차단
|
279
|
-
options.add_argument("--disable-save-password-bubble") # 비밀번호 저장 팝업 차단
|
285
|
+
options.add_preference("profile.password_manager_enabled", false) # 비밀번호 관리자 비활성화
|
286
|
+
options.add_preference("credentials_enable_service", false) # 비밀번호 저장 기능 비활성화
|
287
|
+
#options.add_preference("profile.managed_default_content_settings.cookies", 2) # 쿠키 관련 팝업 차단
|
288
|
+
options.add_preference("profile.default_content_setting_values.notifications", 2) # 알림 차단
|
289
|
+
options.add_argument("--disable-save-password-bubble") # 비밀번호 저장 팝업 차단
|
290
|
+
|
280
291
|
|
292
|
+
# Proxy 설정
|
293
|
+
if proxy != ''
|
294
|
+
options.add_argument('--proxy-server=' + proxy.to_s.force_encoding('utf-8'))
|
295
|
+
end
|
296
|
+
|
297
|
+
# 브라우저 실행
|
298
|
+
begin
|
299
|
+
# 'capabilities'과 'options' 배열로 설정
|
300
|
+
capabilities = Selenium::WebDriver::Remote::Capabilities.chrome
|
301
|
+
capabilities["goog:chromeOptions"] = options.as_json
|
281
302
|
|
282
|
-
|
283
|
-
|
284
|
-
options.add_argument('--proxy-server=' + proxy.to_s.force_encoding('utf-8'))
|
285
|
-
end
|
303
|
+
# Selenium 4에서는 'capabilities'만 사용하는 방식
|
304
|
+
@driver = Selenium::WebDriver.for(:chrome, capabilities: [capabilities, options])
|
286
305
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
306
|
+
@driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: function(){ return false; }});") # 셀레니움 감지 방지
|
307
|
+
|
308
|
+
sleep(1)
|
309
|
+
# 두 번째 탭에서 로그인 페이지 열기
|
310
|
+
@driver.get('https://www.tistory.com/auth/login')
|
311
|
+
sleep(1)
|
292
312
|
|
293
|
-
|
294
|
-
|
313
|
+
# 현재 탭 핸들 저장
|
314
|
+
main_tab = @driver.window_handle
|
295
315
|
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
# 두 번째 탭에서 로그인 페이지 열기
|
300
|
-
@driver.get('https://www.tistory.com/auth/login')
|
301
|
-
sleep(1)
|
302
|
-
|
303
|
-
rescue => e
|
304
|
-
|
305
|
-
puts "Error: #{e.message}"
|
306
|
-
puts 'Using default Chrome driver without proxy'
|
307
|
-
# 'capabilities'과 'options' 배열로 설정
|
308
|
-
capabilities = Selenium::WebDriver::Remote::Capabilities.chrome
|
309
|
-
capabilities["goog:chromeOptions"] = options.as_json
|
316
|
+
# 모든 탭 순회
|
317
|
+
@driver.window_handles.each do |handle|
|
318
|
+
next if handle == main_tab # 현재 탭은 남긴다
|
310
319
|
|
311
|
-
|
312
|
-
|
320
|
+
@driver.switch_to.window(handle)
|
321
|
+
@driver.close # 다른 탭 닫기
|
322
|
+
end
|
313
323
|
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
def login(user_id, user_pw, proxy, captcha_api_key)
|
326
|
-
chrome_start(proxy)
|
327
|
-
@captcha_api_key = captcha_api_key
|
328
|
-
@user_id = user_id
|
324
|
+
# 다시 로그인 탭으로 전환
|
325
|
+
@driver.switch_to.window(main_tab)
|
326
|
+
sleep(1)
|
327
|
+
rescue => e
|
328
|
+
|
329
|
+
puts "Error: #{e.message}"
|
330
|
+
puts 'Using default Chrome driver without proxy'
|
331
|
+
# 'capabilities'과 'options' 배열로 설정
|
332
|
+
capabilities = Selenium::WebDriver::Remote::Capabilities.chrome
|
333
|
+
capabilities["goog:chromeOptions"] = options.as_json
|
329
334
|
|
330
|
-
|
331
|
-
|
332
|
-
Dir.entries('./cookie').each do |i|
|
333
|
-
if i != '.' && i != '..'
|
334
|
-
user_cookie_file << i
|
335
|
-
end
|
336
|
-
end
|
337
|
-
rescue
|
338
|
-
end
|
335
|
+
# Selenium 4에서는 'capabilities'만 사용하는 방식
|
336
|
+
@driver = Selenium::WebDriver.for(:chrome, capabilities: [capabilities, options])
|
339
337
|
|
340
|
-
|
341
|
-
if user_cookie_file.include?(user_id+'.txt')
|
342
|
-
f = File.open('./cookie/'+user_id+'.txt', 'r')
|
343
|
-
@cookie4 = JSON.parse(f.read)
|
344
|
-
f.close
|
345
|
-
end
|
338
|
+
@driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: function(){ return false; }});") # 셀레니움 감지 방지
|
346
339
|
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
340
|
+
# 첫 번째 탭에서 확장 프로그램을 로드
|
341
|
+
#@driver.get("chrome-extension://ifibfemgeogfhoebkmokieepdoobkbpo/options/options.html")
|
342
|
+
sleep(1)
|
343
|
+
# 두 번째 탭에서 로그인 페이지 열기
|
344
|
+
@driver.get('https://www.tistory.com/auth/login')
|
345
|
+
sleep(1)
|
346
|
+
|
347
|
+
# 현재 탭 핸들 저장
|
348
|
+
main_tab = @driver.window_handle
|
349
|
+
|
350
|
+
# 모든 탭 순회
|
351
|
+
@driver.window_handles.each do |handle|
|
352
|
+
next if handle == main_tab # 현재 탭은 남긴다
|
353
|
+
|
354
|
+
@driver.switch_to.window(handle)
|
355
|
+
@driver.close # 다른 탭 닫기
|
356
|
+
end
|
357
|
+
|
358
|
+
# 다시 로그인 탭으로 전환
|
359
|
+
@driver.switch_to.window(main_tab)
|
360
|
+
sleep(1)
|
351
361
|
end
|
352
|
-
rescue
|
353
362
|
end
|
354
363
|
|
355
|
-
|
356
|
-
|
357
|
-
|
364
|
+
def login(user_id, user_pw, proxy, captcha_api_key)
|
365
|
+
chrome_start(proxy)
|
366
|
+
@captcha_api_key = captcha_api_key
|
367
|
+
@user_id = user_id
|
358
368
|
|
369
|
+
if captcha_api_key != 'captcha_api_key 입력' && !captcha_api_key.to_s.strip.empty?
|
370
|
+
puts "캡처 키 확인 됨: '#{captcha_api_key}'".red
|
371
|
+
@driver.switch_to.new_window(:tab)
|
372
|
+
sleep(1.5)
|
373
|
+
@driver.switch_to.window(@driver.window_handles[1])
|
374
|
+
@driver.navigate.to("chrome-extension://ifibfemgeogfhoebkmokieepdoobkbpo/options/options.html")
|
375
|
+
sleep(3)
|
376
|
+
@driver.find_element(:xpath, '//*[@name="apiKey"]').click
|
377
|
+
Clipboard.copy(captcha_api_key)
|
378
|
+
sleep(0.5)
|
379
|
+
@driver.action.key_down(:control).send_keys('v').key_up(:control).perform
|
380
|
+
sleep(0.5)
|
381
|
+
@driver.find_element(:xpath, '//*[@data-lang="login"]').click
|
382
|
+
|
383
|
+
begin
|
384
|
+
sleep(2)
|
385
|
+
@driver.switch_to.alert.dismiss
|
386
|
+
sleep(1)
|
387
|
+
rescue
|
388
|
+
end
|
389
|
+
@driver.close
|
390
|
+
sleep(1)
|
391
|
+
@driver.switch_to.window(@driver.window_handles[0])
|
392
|
+
sleep(2)
|
393
|
+
else
|
394
|
+
end
|
395
|
+
|
396
|
+
user_cookie_file = []
|
397
|
+
begin
|
398
|
+
Dir.entries('./cookie').each do |i|
|
399
|
+
if i != '.' && i != '..'
|
400
|
+
user_cookie_file << i
|
401
|
+
end
|
402
|
+
end
|
403
|
+
rescue
|
404
|
+
end
|
405
|
+
|
406
|
+
@cookie4 = {}
|
407
|
+
if user_cookie_file.include?(user_id+'.txt')
|
408
|
+
f = File.open('./cookie/'+user_id+'.txt', 'r')
|
409
|
+
@cookie4 = JSON.parse(f.read)
|
410
|
+
f.close
|
411
|
+
end
|
412
|
+
|
413
|
+
|
414
|
+
|
415
|
+
# 기존 쿠키가 있으면 쿠키를 추가
|
416
|
+
begin
|
417
|
+
@cookie4.each do |i|
|
418
|
+
@driver.manage.add_cookie(name: i['name'], value: i['value'], same_site: i['same_site'], domain: i['domain'], path: i['path'])
|
419
|
+
end
|
420
|
+
rescue
|
421
|
+
end
|
422
|
+
|
423
|
+
|
424
|
+
sleep(1.5)
|
425
|
+
@driver.get('https://www.tistory.com/auth/login')
|
359
426
|
sleep(1)
|
427
|
+
@driver.switch_to.window(@driver.window_handles[0])
|
428
|
+
|
429
|
+
|
360
430
|
begin
|
361
431
|
wait = Selenium::WebDriver::Wait.new(:timeout => 3)
|
362
432
|
wait.until { @driver.find_element(:xpath, '//*[@id="cMain"]/div/div/div/div/a[2]/span[2]') }
|
@@ -394,25 +464,7 @@ class Naver
|
|
394
464
|
wait.until { @driver.find_element(:xpath, '//*[@id="captchaContainer"]') }
|
395
465
|
puts '-[√] 로그인 중 캡챠 요구 발생 처리 진행.......'.yellow
|
396
466
|
sleep(1)
|
397
|
-
|
398
|
-
sleep(1)
|
399
|
-
@driver.find_element(:xpath, '//*[@name="apiKey"]').click
|
400
|
-
Clipboard.copy(captcha_api_key)
|
401
|
-
sleep(0.5)
|
402
|
-
@driver.action.key_down(:control).send_keys('v').key_up(:control).perform
|
403
|
-
sleep(0.5)
|
404
|
-
@driver.find_element(:xpath, '//*[@data-lang="login"]').click
|
405
|
-
|
406
|
-
begin
|
407
|
-
sleep(2)
|
408
|
-
@driver.switch_to.alert.dismiss
|
409
|
-
sleep(1)
|
410
|
-
rescue
|
411
|
-
|
412
|
-
end
|
413
|
-
|
414
|
-
# 두 번째 탭으로 전환
|
415
|
-
@driver.switch_to.window(@driver.window_handles[1])
|
467
|
+
|
416
468
|
|
417
469
|
begin
|
418
470
|
wait = Selenium::WebDriver::Wait.new(:timeout => 7)
|
@@ -518,6 +570,7 @@ class Naver
|
|
518
570
|
|
519
571
|
|
520
572
|
|
573
|
+
|
521
574
|
def update(title, content, option, url, keyword, captcha_api_key)#dd_time
|
522
575
|
puts 'start...'.yellow
|
523
576
|
puts(url)
|
@@ -540,20 +593,6 @@ class Naver
|
|
540
593
|
end
|
541
594
|
|
542
595
|
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
596
|
#@driver.manage.window.maximize
|
558
597
|
#창 크기 최대화
|
559
598
|
category2 = option['category'].to_s
|
@@ -578,12 +617,6 @@ class Naver
|
|
578
617
|
end
|
579
618
|
|
580
619
|
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
620
|
sleep(1)
|
588
621
|
|
589
622
|
begin
|
@@ -596,10 +629,6 @@ class Naver
|
|
596
629
|
puts e
|
597
630
|
end
|
598
631
|
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
632
|
|
604
633
|
|
605
634
|
sleep(1)
|
@@ -1239,29 +1268,7 @@ class Naver
|
|
1239
1268
|
wait.until { @driver.find_element(:xpath, '//*[@id="captchaContainer"]') }
|
1240
1269
|
puts '-[√] 로그인 중 캡챠 요구 발생 처리 진행.......'.yellow
|
1241
1270
|
sleep(1)
|
1242
|
-
@driver.switch_to.window(@driver.window_handles[0])
|
1243
|
-
sleep(1)
|
1244
|
-
@driver.find_element(:xpath, '//*[@name="apiKey"]').click
|
1245
|
-
sleep(0.5)
|
1246
|
-
@driver.find_element(:xpath, '//*[@name="apiKey"]').clear
|
1247
|
-
sleep(0.5)
|
1248
|
-
Clipboard.copy(captcha_api_key)
|
1249
|
-
sleep(0.5)
|
1250
|
-
@driver.action.key_down(:control).send_keys('v').key_up(:control).perform
|
1251
|
-
sleep(0.5)
|
1252
|
-
@driver.find_element(:xpath, '//*[@data-lang="login"]').click
|
1253
1271
|
|
1254
|
-
begin
|
1255
|
-
sleep(2)
|
1256
|
-
@driver.switch_to.alert.dismiss
|
1257
|
-
sleep(1)
|
1258
|
-
rescue
|
1259
|
-
|
1260
|
-
end
|
1261
|
-
|
1262
|
-
# 두 번째 탭으로 전환
|
1263
|
-
@driver.switch_to.window(@driver.window_handles[1])
|
1264
|
-
|
1265
1272
|
begin
|
1266
1273
|
wait = Selenium::WebDriver::Wait.new(:timeout => 7)
|
1267
1274
|
wait.until { @driver.find_element(:xpath, '//*[@data-state="ready"]') }
|
@@ -1390,7 +1397,7 @@ class Naver
|
|
1390
1397
|
@driver.find_element(:xpath, '//*[@id="publish-btn"]').click #등록완료버튼
|
1391
1398
|
rescue
|
1392
1399
|
# 타임아웃을 100초로 설정
|
1393
|
-
wait = Selenium::WebDriver::Wait.new(:timeout =>
|
1400
|
+
wait = Selenium::WebDriver::Wait.new(:timeout => 120)
|
1394
1401
|
# 요소가 나타날 때까지 100초 동안 기다립니다.
|
1395
1402
|
wait.until { @driver.find_element(:xpath, '//*[@data-state="solved"]') }
|
1396
1403
|
sleep(2)
|
@@ -1436,14 +1443,7 @@ class Naver
|
|
1436
1443
|
rescue
|
1437
1444
|
end
|
1438
1445
|
end
|
1439
|
-
|
1440
|
-
|
1441
|
-
|
1442
|
-
|
1443
|
-
|
1444
|
-
|
1445
|
-
|
1446
|
-
|
1446
|
+
|
1447
1447
|
|
1448
1448
|
end
|
1449
1449
|
end
|
@@ -1637,21 +1637,81 @@ class Wordpress
|
|
1637
1637
|
|
1638
1638
|
|
1639
1639
|
|
1640
|
-
def auto_image
|
1640
|
+
def auto_image(keyword = nil)
|
1641
|
+
keyword ||= @keyword
|
1642
|
+
puts "키워드: #{keyword}"
|
1643
|
+
|
1644
|
+
client = HTTPClient.new
|
1645
|
+
client.default_header = {
|
1646
|
+
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '\
|
1647
|
+
'(KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36',
|
1648
|
+
'Accept' => 'application/json, text/javascript, */*; q=0.01',
|
1649
|
+
'Accept-Language' => 'en-US,en;q=0.9',
|
1650
|
+
'Referer' => "https://unsplash.com/s/photos/#{URI.encode_www_form_component(keyword)}",
|
1651
|
+
'X-Requested-With' => 'XMLHttpRequest'
|
1652
|
+
}
|
1653
|
+
|
1654
|
+
retry_count = 0
|
1655
|
+
max_retries = 10
|
1656
|
+
results = []
|
1657
|
+
|
1641
1658
|
begin
|
1642
1659
|
page = rand(1..15)
|
1643
|
-
|
1644
|
-
|
1645
|
-
|
1646
|
-
|
1647
|
-
|
1660
|
+
url = "https://unsplash.com/napi/search/photos?query=#{URI.encode_www_form_component(keyword)}&page=#{page}&per_page=20"
|
1661
|
+
puts "Request URL: #{url}"
|
1662
|
+
res = client.get(url)
|
1663
|
+
|
1664
|
+
unless res.status == 200
|
1665
|
+
puts "HTTP Error: #{res.status}"
|
1666
|
+
raise "HTTP Error"
|
1648
1667
|
end
|
1649
|
-
|
1650
|
-
|
1651
|
-
|
1652
|
-
|
1653
|
-
|
1668
|
+
|
1669
|
+
json = JSON.parse(res.body)
|
1670
|
+
results = json['results']
|
1671
|
+
mm = []
|
1672
|
+
|
1673
|
+
results.each do |photo|
|
1674
|
+
full_url = photo.dig('urls', 'full').to_s
|
1675
|
+
regular_url = photo.dig('urls', 'regular').to_s
|
1676
|
+
|
1677
|
+
if full_url.start_with?("https://images.unsplash.com/photo-") &&
|
1678
|
+
regular_url.include?("1080")
|
1679
|
+
mm << full_url
|
1680
|
+
end
|
1681
|
+
end
|
1682
|
+
|
1683
|
+
if mm.empty?
|
1684
|
+
raise "No matching image"
|
1685
|
+
end
|
1686
|
+
|
1687
|
+
selected_url = mm.sample
|
1688
|
+
Down.download(selected_url, destination: "./image/memory.png")
|
1689
|
+
puts "이미지 다운로드 완료: #{selected_url}"
|
1690
|
+
|
1691
|
+
rescue => e
|
1692
|
+
retry_count += 1
|
1693
|
+
puts "auto_image 에러: #{e.message} (재시도 #{retry_count}/#{max_retries})"
|
1694
|
+
sleep(3)
|
1695
|
+
if retry_count < max_retries
|
1654
1696
|
retry
|
1697
|
+
else
|
1698
|
+
puts "최대 재시도 초과. 조건 무시하고 랜덤 이미지 다운로드 시도..."
|
1699
|
+
|
1700
|
+
if results && !results.empty?
|
1701
|
+
random_photo = results.sample
|
1702
|
+
fallback_url = random_photo.dig('urls', 'full')
|
1703
|
+
if fallback_url
|
1704
|
+
Down.download(fallback_url, destination: "./image/memory.png")
|
1705
|
+
puts "랜덤 이미지 다운로드 완료: #{fallback_url}"
|
1706
|
+
else
|
1707
|
+
puts "랜덤 이미지 URL을 찾을 수 없습니다. 단색 배경 이미지 생성합니다."
|
1708
|
+
color_image
|
1709
|
+
end
|
1710
|
+
else
|
1711
|
+
puts "이미지 결과가 없어 다운로드할 수 없습니다. 단색 배경 이미지 생성합니다."
|
1712
|
+
color_image
|
1713
|
+
end
|
1714
|
+
end
|
1655
1715
|
end
|
1656
1716
|
end
|
1657
1717
|
|
@@ -1663,35 +1723,52 @@ class Wordpress
|
|
1663
1723
|
|
1664
1724
|
def save_image
|
1665
1725
|
if @data['이미지설정']['이미지'].length == 0
|
1666
|
-
|
1726
|
+
return
|
1727
|
+
end
|
1728
|
+
|
1729
|
+
if @data['이미지설정']['순서사용'].checked?
|
1730
|
+
image_path = @data['이미지설정']['이미지'][@image_counter][2]
|
1731
|
+
@image_counter += 1
|
1732
|
+
if @image_counter > @data['이미지설정']['이미지'].length - 1
|
1733
|
+
@image_counter = 0
|
1734
|
+
end
|
1667
1735
|
else
|
1668
|
-
|
1669
|
-
|
1670
|
-
|
1671
|
-
|
1672
|
-
@image_counter = 0
|
1673
|
-
end
|
1674
|
-
else
|
1675
|
-
image_path = @data['이미지설정']['이미지'].sample[2]
|
1736
|
+
# 초기화가 안됐거나 다 썼으면 새롭게 섞는다
|
1737
|
+
@shuffled_images ||= []
|
1738
|
+
if @shuffled_images.empty?
|
1739
|
+
@shuffled_images = @data['이미지설정']['이미지'].shuffle
|
1676
1740
|
end
|
1677
|
-
|
1678
|
-
|
1741
|
+
|
1742
|
+
image_path = @shuffled_images.shift[2]
|
1679
1743
|
end
|
1744
|
+
|
1745
|
+
img = Magick::Image.read(image_path).first
|
1746
|
+
img.write('./image/memory.png')
|
1680
1747
|
end
|
1681
1748
|
|
1682
1749
|
def change_image_size(w)
|
1683
1750
|
img = Magick::Image.read('./image/memory.png').first
|
1684
1751
|
width = img.columns
|
1685
1752
|
height = img.rows
|
1686
|
-
|
1687
|
-
|
1688
|
-
|
1689
|
-
|
1690
|
-
|
1753
|
+
|
1754
|
+
# '원본'이 선택된 경우, 리사이징을 하지 않고 원본 이미지를 그대로 반환
|
1755
|
+
if w == 'original'
|
1756
|
+
return img # 원본 이미지 그대로 반환
|
1757
|
+
else
|
1758
|
+
begin
|
1759
|
+
if @data['image_type'][0].checked? or @data['image_type'][2].checked?
|
1760
|
+
# 비율을 맞추어 리사이징
|
1761
|
+
img.resize!(w, w * (height.to_f / width.to_f))
|
1762
|
+
else
|
1763
|
+
# 정사각형으로 리사이징
|
1764
|
+
img.resize!(w, w)
|
1765
|
+
end
|
1766
|
+
rescue
|
1767
|
+
img.resize!(w, w) # 예외 처리 시에도 리사이징
|
1691
1768
|
end
|
1692
|
-
rescue
|
1693
|
-
img.resize!(w, w)
|
1694
1769
|
end
|
1770
|
+
|
1771
|
+
# 리사이징된 이미지 저장
|
1695
1772
|
img.write('./image/memory.png')
|
1696
1773
|
end
|
1697
1774
|
|
@@ -1763,22 +1840,28 @@ class Wordpress
|
|
1763
1840
|
auto_image()
|
1764
1841
|
end
|
1765
1842
|
|
1766
|
-
|
1843
|
+
# '원본'을 포함한 이미지 크기 옵션 추가
|
1844
|
+
image_size = [480, 740, 650, 550, 480, 'original']
|
1767
1845
|
size = 0
|
1768
|
-
|
1846
|
+
|
1847
|
+
for n in 0..5 # 0부터 5까지 반복, '원본' 옵션까지 포함
|
1769
1848
|
if @data['image_size'][n].checked?
|
1770
|
-
if n ==
|
1771
|
-
size =
|
1849
|
+
if n == 5 # '원본'이 선택되었을 경우
|
1850
|
+
size = 'original'
|
1851
|
+
elsif n == 0
|
1852
|
+
size = image_size.sample # 랜덤 선택
|
1772
1853
|
else
|
1773
1854
|
size = image_size[n]
|
1774
1855
|
end
|
1775
1856
|
end
|
1776
1857
|
end
|
1858
|
+
|
1859
|
+
# '원본'이 선택되지 않았다면 기본 값 설정
|
1777
1860
|
if size == 0
|
1778
1861
|
size = 480
|
1779
1862
|
end
|
1780
|
-
|
1781
|
-
change_image_size(size)
|
1863
|
+
|
1864
|
+
change_image_size(size) # 크기 변경 함수 호출
|
1782
1865
|
|
1783
1866
|
if @data['이미지설정']['필터사용'].checked?
|
1784
1867
|
image_filter()
|
@@ -2173,6 +2256,22 @@ class Wordpress
|
|
2173
2256
|
@data['table'].pop
|
2174
2257
|
#제목끝
|
2175
2258
|
# content = " #{content} "
|
2259
|
+
if @data['포스트설정']['gpt키워드'].checked?
|
2260
|
+
gpt_keyword_prompt = @data['포스트설정']['gpt키워드_프롬프트'].text.to_s.force_encoding('utf-8')
|
2261
|
+
gpt_keyword_prompt_sample = gpt_keyword_prompt.strip.empty? ? "프롬프트: 관련된 글을 1500자에서 2500자 사이로 만들어줘" : gpt_keyword_prompt
|
2262
|
+
chat = Chat.new(@data['포스트설정']['api_key'].text.to_s.force_encoding('utf-8'), gpt_keyword_prompt)
|
2263
|
+
gpt_text = chat.message(keyword)
|
2264
|
+
#content = content.to_s + "\n(자동생성글)\n" + gpt_text.to_s
|
2265
|
+
content = content.to_s + "(자동생성글)" + gpt_text.to_s
|
2266
|
+
elsif @data['포스트설정']['내용을자동생성'].checked?
|
2267
|
+
content = auto_text
|
2268
|
+
elsif @data['포스트설정']['내용과자동생성'].checked?
|
2269
|
+
#content = content + "\n(자동생성글)\n" + auto_text
|
2270
|
+
content = content + "(자동생성글)" + auto_text
|
2271
|
+
end
|
2272
|
+
@data['table'][index][-1] = 45
|
2273
|
+
@data['table'] << []
|
2274
|
+
@data['table'].pop
|
2176
2275
|
|
2177
2276
|
if @data['포스트설정']['특정단어굵기'].checked?
|
2178
2277
|
content2 = ''
|
@@ -2212,7 +2311,7 @@ class Wordpress
|
|
2212
2311
|
content = content2
|
2213
2312
|
end
|
2214
2313
|
end
|
2215
|
-
@data['table'][index][-1] =
|
2314
|
+
@data['table'][index][-1] = 50
|
2216
2315
|
@data['table'] << []
|
2217
2316
|
@data['table'].pop
|
2218
2317
|
if @data['포스트설정']['단어크기변경'].checked?
|
@@ -2233,19 +2332,7 @@ class Wordpress
|
|
2233
2332
|
@data['table'][index][-1] = 50
|
2234
2333
|
@data['table'] << []
|
2235
2334
|
@data['table'].pop
|
2236
|
-
|
2237
|
-
gpt_keyword_prompt = @data['포스트설정']['gpt키워드_프롬프트'].text.to_s.force_encoding('utf-8')
|
2238
|
-
gpt_keyword_prompt_sample = gpt_keyword_prompt.strip.empty? ? "프롬프트: 관련된 글을 1500자에서 2500자 사이로 만들어줘" : gpt_keyword_prompt
|
2239
|
-
chat = Chat.new(@data['포스트설정']['api_key'].text.to_s.force_encoding('utf-8'), gpt_keyword_prompt)
|
2240
|
-
gpt_text = chat.message(keyword)
|
2241
|
-
#content = content.to_s + "\n(자동생성글)\n" + gpt_text.to_s
|
2242
|
-
content = content.to_s + "(자동생성글)" + gpt_text.to_s
|
2243
|
-
elsif @data['포스트설정']['내용을자동생성'].checked?
|
2244
|
-
content = auto_text
|
2245
|
-
elsif @data['포스트설정']['내용과자동생성'].checked?
|
2246
|
-
#content = content + "\n(자동생성글)\n" + auto_text
|
2247
|
-
content = content + "(자동생성글)" + auto_text
|
2248
|
-
end
|
2335
|
+
|
2249
2336
|
|
2250
2337
|
if @data['포스트설정']['내용키워드삽입'].checked?
|
2251
2338
|
puts '내용키워드삽입...'
|
@@ -2427,7 +2514,6 @@ class Wordpress
|
|
2427
2514
|
|
2428
2515
|
if @data['포스트설정']['내용사진자동삽입'].checked?
|
2429
2516
|
puts '내용사진자동삽입...'
|
2430
|
-
|
2431
2517
|
sn = @data['포스트설정']['내용사진자동삽입시작숫자'].text.to_s.force_encoding('utf-8').to_i
|
2432
2518
|
en = @data['포스트설정']['내용사진자동삽입끝숫자'].text.to_s.force_encoding('utf-8').to_i
|
2433
2519
|
|
@@ -2439,54 +2525,71 @@ class Wordpress
|
|
2439
2525
|
end
|
2440
2526
|
|
2441
2527
|
if cn != 0
|
2442
|
-
#
|
2443
|
-
|
2444
|
-
|
2445
|
-
|
2446
|
-
|
2447
|
-
|
2448
|
-
|
2449
|
-
|
2450
|
-
|
2451
|
-
|
2452
|
-
missing_empty_lines = cn - empty_positions.length
|
2453
|
-
missing_empty_lines.times do
|
2454
|
-
content5 << "" # 텍스트 마지막에 빈 줄 추가
|
2528
|
+
# content5 구성 및 위치 배열 생성
|
2529
|
+
if @data['포스트설정']['내용과자동생성'].checked?
|
2530
|
+
if @data['포스트설정']['자동글 수식에 입력'].checked?
|
2531
|
+
content5 = content.split("(자동생성글)")[0].to_s.split("\n")
|
2532
|
+
content55 = content.split("(자동생성글)")[1].to_s
|
2533
|
+
position = content5.length.times.to_a.sample(cn).sort.reverse
|
2534
|
+
else
|
2535
|
+
content5 = content.split("(자동생성글)")[0].to_s.split("\n")
|
2536
|
+
content55 = content.split("(자동생성글)")[1].to_s
|
2537
|
+
position = content5.length.times.to_a.sample(cn).sort.reverse
|
2455
2538
|
end
|
2456
|
-
|
2539
|
+
elsif @data['포스트설정']['gpt키워드'].checked?
|
2540
|
+
content5 = content.split("(자동생성글)")[1].to_s.split("\n")
|
2541
|
+
content_prefix = content.split("(자동생성글)")[0].to_s
|
2542
|
+
content55 = ''
|
2543
|
+
position = content5.length.times.to_a.sample(cn).sort.reverse
|
2544
|
+
else
|
2545
|
+
content5 = content.split("\n")
|
2546
|
+
content55 = ''
|
2547
|
+
position = content5.length.times.to_a.sample(cn).sort.reverse
|
2457
2548
|
end
|
2458
2549
|
|
2459
|
-
#
|
2460
|
-
|
2550
|
+
# 중복 필터링 로직
|
2551
|
+
while true
|
2552
|
+
check11 = 0
|
2553
|
+
position.each_with_index do |pos, idx|
|
2554
|
+
if content5[pos].to_s.include?('style') || content5[pos].to_s.include?('<') || content5[pos].to_s.include?('>')
|
2555
|
+
check11 = 1
|
2556
|
+
position[idx] += 4
|
2557
|
+
end
|
2558
|
+
end
|
2559
|
+
break if check11 == 0
|
2560
|
+
end
|
2461
2561
|
|
2462
|
-
# 이미지 URL 가져오기
|
2463
2562
|
if @data['포스트설정']['내용과자동생성'].checked? || @data['포스트설정']['gpt키워드'].checked?
|
2464
|
-
|
2563
|
+
sleep(2)
|
2564
|
+
puts '이미지 자동 세탁 중 · · · '
|
2465
2565
|
end
|
2466
|
-
|
2467
|
-
|
2566
|
+
|
2567
|
+
p content5
|
2568
|
+
puts content55
|
2569
|
+
p position
|
2570
|
+
|
2571
|
+
sleep(2)
|
2468
2572
|
position.each do |i|
|
2469
|
-
|
2470
|
-
puts '사진넣는위치 => ' + i.to_s
|
2471
|
-
|
2472
|
-
# 링크가 있을 경우 링크 포함
|
2573
|
+
image_url22 = get_image_file().force_encoding('utf-8')
|
2473
2574
|
if @data['포스트설정']['내용사진링크'].checked?
|
2474
|
-
|
2475
|
-
|
2575
|
+
image_tag = '<a href="' + @data['포스트설정']['내용사진링크값'].text.to_s.force_encoding('utf-8') +
|
2576
|
+
'"><img src="' + image_url22 + '" alt="' + keyword.force_encoding('utf-8') + '" class="aligncenter size-full"></a>'
|
2476
2577
|
else
|
2477
|
-
|
2478
|
-
content5[i] = '**image**' # 빈 줄에 이미지를 삽입
|
2578
|
+
image_tag = '<img src="' + image_url22 + '" alt="' + keyword + '" class="aligncenter size-full">'
|
2479
2579
|
end
|
2580
|
+
content5.insert(i, image_tag)
|
2480
2581
|
end
|
2481
2582
|
|
2482
|
-
|
2483
|
-
|
2583
|
+
sleep(2)
|
2584
|
+
puts '이미지 자동 세탁 완료 · · · '
|
2585
|
+
|
2586
|
+
if @data['포스트설정']['내용과자동생성'].checked?
|
2484
2587
|
content = content5.join("\n") + '(자동생성글)' + content55
|
2588
|
+
elsif @data['포스트설정']['gpt키워드'].checked?
|
2589
|
+
content = content_prefix + "(자동생성글)" + content5.join("\n")
|
2485
2590
|
else
|
2486
2591
|
content = content5.join("\n")
|
2487
2592
|
end
|
2488
|
-
|
2489
|
-
puts content
|
2490
2593
|
end
|
2491
2594
|
end
|
2492
2595
|
|
@@ -2498,12 +2601,22 @@ class Wordpress
|
|
2498
2601
|
content = content_memory[0]
|
2499
2602
|
content_end = content_memory[1].to_s
|
2500
2603
|
|
2604
|
+
if @data['포스트설정']['gpt키워드'].checked?
|
2605
|
+
if @data['포스트설정']['gpt상단'].checked?
|
2606
|
+
content = "(자동생성글)\n" + content_end + "\n" + content
|
2607
|
+
else
|
2608
|
+
content = content + "\n(자동생성글)\n" + content_end
|
2609
|
+
end
|
2610
|
+
else
|
2611
|
+
content = content + "\n(자동생성글)\n" + content_end
|
2612
|
+
end
|
2613
|
+
|
2501
2614
|
if @data['포스트설정']['특정단어키워드로변경'].checked?
|
2502
2615
|
@data['포스트설정']['특정단어키워드로변경값'].text.to_s.force_encoding('utf-8').split(',').each do |i|
|
2503
2616
|
content = content.split(i.force_encoding('utf-8')).join(keyword)
|
2504
2617
|
end
|
2505
2618
|
end
|
2506
|
-
|
2619
|
+
|
2507
2620
|
@data['table'][index][-1] = 75
|
2508
2621
|
@data['table'] << []
|
2509
2622
|
@data['table'].pop
|
@@ -2619,11 +2732,76 @@ class Wordpress
|
|
2619
2732
|
# end
|
2620
2733
|
#end
|
2621
2734
|
|
2622
|
-
|
2623
|
-
@data['포스트설정']['
|
2624
|
-
|
2735
|
+
if @data['포스트설정']['지도로변경'].checked?
|
2736
|
+
map_address = @data['포스트설정']['지도주소'].text.to_s.force_encoding('utf-8')
|
2737
|
+
change_words = @data['포스트설정']['지도로변경단어'].text.to_s.force_encoding('utf-8').split(',')
|
2738
|
+
|
2739
|
+
content_lines = content.split("\n")
|
2740
|
+
new_lines = []
|
2741
|
+
|
2742
|
+
content_lines.each do |line|
|
2743
|
+
# 이미 처리된 줄은 건너뜀
|
2744
|
+
if line.include?("<koreamap>")
|
2745
|
+
new_lines << line
|
2746
|
+
next
|
2747
|
+
end
|
2748
|
+
|
2749
|
+
processed = false
|
2750
|
+
|
2751
|
+
change_words.each do |change_word|
|
2752
|
+
# 1. [단어][주소] 패턴 처리: 분리 + 출력
|
2753
|
+
if line =~ /#{Regexp.escape(change_word)}\[(.*?)\]/
|
2754
|
+
match_data = line.match(/(.*)#{Regexp.escape(change_word)}\[(.*?)\](.*)/)
|
2755
|
+
if match_data
|
2756
|
+
before = match_data[1]
|
2757
|
+
address = match_data[2]
|
2758
|
+
after = match_data[3]
|
2759
|
+
|
2760
|
+
# 태그 추출
|
2761
|
+
open_tag = before[/<p[^>]*?>/i] || ""
|
2762
|
+
close_tag = after[/<\/p>/i] || ""
|
2763
|
+
|
2764
|
+
# 텍스트 정리
|
2765
|
+
before_text = before.sub(open_tag, '')
|
2766
|
+
after_text = after.sub(close_tag, '')
|
2767
|
+
|
2768
|
+
# 줄 분리
|
2769
|
+
new_lines << "#{open_tag}#{before_text}</p>" unless before_text.strip.empty?
|
2770
|
+
new_lines << "<koreamap>#{address}</koreamap>"
|
2771
|
+
new_lines << "#{open_tag}#{after_text}#{close_tag}" unless after_text.strip.empty?
|
2772
|
+
|
2773
|
+
processed = true
|
2774
|
+
break
|
2775
|
+
end
|
2776
|
+
end
|
2777
|
+
|
2778
|
+
# 2. 일반 단어 처리 (한 줄에만 등장하는 경우)
|
2779
|
+
if !processed && line.include?(change_word)
|
2780
|
+
parts = line.split(change_word, 2)
|
2781
|
+
prefix = parts[0]
|
2782
|
+
suffix = parts[1]
|
2783
|
+
|
2784
|
+
open_tag = prefix[/<p[^>]*?>/i] || ""
|
2785
|
+
close_tag = suffix[/<\/p>/i] || ""
|
2786
|
+
|
2787
|
+
prefix_text = prefix.sub(open_tag, '')
|
2788
|
+
suffix_text = suffix.sub(close_tag, '')
|
2789
|
+
|
2790
|
+
new_lines << "#{open_tag}#{prefix_text}</p>" unless prefix_text.strip.empty?
|
2791
|
+
new_lines << "<koreamap>#{map_address}</koreamap>"
|
2792
|
+
new_lines << "#{open_tag}#{suffix_text}#{close_tag}" unless suffix_text.strip.empty?
|
2793
|
+
|
2794
|
+
processed = true
|
2795
|
+
break
|
2796
|
+
end
|
2797
|
+
end
|
2798
|
+
|
2799
|
+
# 변경이 없었으면 원래 줄 추가
|
2800
|
+
new_lines << line unless processed
|
2625
2801
|
end
|
2626
|
-
|
2802
|
+
|
2803
|
+
content = new_lines.join("\n")
|
2804
|
+
end
|
2627
2805
|
|
2628
2806
|
|
2629
2807
|
|
@@ -2637,15 +2815,18 @@ class Wordpress
|
|
2637
2815
|
# content = content
|
2638
2816
|
# soosick_1 = content_end
|
2639
2817
|
#else
|
2640
|
-
|
2641
|
-
|
2642
|
-
|
2643
|
-
|
2644
|
-
|
2645
|
-
|
2818
|
+
parts = content.split('(자동생성글)', 2)
|
2819
|
+
content_main = parts[0].strip
|
2820
|
+
content_end = parts[1].to_s.strip
|
2821
|
+
|
2822
|
+
if @data['포스트설정']['gpt키워드'].checked?
|
2823
|
+
if @data['포스트설정']['gpt상단'].checked?
|
2824
|
+
content = content_end + "\n" + content_main + "\n"
|
2646
2825
|
else
|
2647
|
-
|
2648
|
-
|
2826
|
+
content = content_main + "\n" + content_end + "\n"
|
2827
|
+
end
|
2828
|
+
else
|
2829
|
+
content = content_main + "\n" + content_end + "\n"
|
2649
2830
|
end
|
2650
2831
|
|
2651
2832
|
|
@@ -3934,6 +4115,7 @@ class Wordpress
|
|
3934
4115
|
@data['image_size'][2].checked = false
|
3935
4116
|
@data['image_size'][3].checked = false
|
3936
4117
|
@data['image_size'][4].checked = false
|
4118
|
+
@data['image_size'][5].checked = false
|
3937
4119
|
end
|
3938
4120
|
}
|
3939
4121
|
}
|
@@ -3944,6 +4126,7 @@ class Wordpress
|
|
3944
4126
|
@data['image_size'][2].checked = false
|
3945
4127
|
@data['image_size'][3].checked = false
|
3946
4128
|
@data['image_size'][4].checked = false
|
4129
|
+
@data['image_size'][5].checked = false
|
3947
4130
|
end
|
3948
4131
|
}
|
3949
4132
|
}
|
@@ -3954,6 +4137,7 @@ class Wordpress
|
|
3954
4137
|
@data['image_size'][0].checked = false
|
3955
4138
|
@data['image_size'][3].checked = false
|
3956
4139
|
@data['image_size'][4].checked = false
|
4140
|
+
@data['image_size'][5].checked = false
|
3957
4141
|
end
|
3958
4142
|
}
|
3959
4143
|
}
|
@@ -3964,6 +4148,7 @@ class Wordpress
|
|
3964
4148
|
@data['image_size'][2].checked = false
|
3965
4149
|
@data['image_size'][0].checked = false
|
3966
4150
|
@data['image_size'][4].checked = false
|
4151
|
+
@data['image_size'][5].checked = false
|
3967
4152
|
end
|
3968
4153
|
}
|
3969
4154
|
}
|
@@ -3974,6 +4159,18 @@ class Wordpress
|
|
3974
4159
|
@data['image_size'][2].checked = false
|
3975
4160
|
@data['image_size'][3].checked = false
|
3976
4161
|
@data['image_size'][0].checked = false
|
4162
|
+
@data['image_size'][5].checked = false
|
4163
|
+
end
|
4164
|
+
}
|
4165
|
+
}
|
4166
|
+
@data['image_size'][5] = checkbox('원본 px'){
|
4167
|
+
on_toggled{
|
4168
|
+
if @data['image_size'][5].checked?
|
4169
|
+
@data['image_size'][1].checked = false
|
4170
|
+
@data['image_size'][2].checked = false
|
4171
|
+
@data['image_size'][3].checked = false
|
4172
|
+
@data['image_size'][0].checked = false
|
4173
|
+
@data['image_size'][4].checked = false
|
3977
4174
|
end
|
3978
4175
|
}
|
3979
4176
|
}
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tblog_duopack
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.50
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- zon
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date: 2025-
|
10
|
+
date: 2025-05-23 00:00:00.000000000 Z
|
12
11
|
dependencies: []
|
13
12
|
description: File to Clipboard gem
|
14
13
|
email: mymin26@naver.com
|
@@ -21,7 +20,6 @@ homepage: ''
|
|
21
20
|
licenses:
|
22
21
|
- zon
|
23
22
|
metadata: {}
|
24
|
-
post_install_message:
|
25
23
|
rdoc_options: []
|
26
24
|
require_paths:
|
27
25
|
- lib
|
@@ -36,8 +34,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
36
34
|
- !ruby/object:Gem::Version
|
37
35
|
version: '0'
|
38
36
|
requirements: []
|
39
|
-
rubygems_version: 3.
|
40
|
-
signing_key:
|
37
|
+
rubygems_version: 3.6.7
|
41
38
|
specification_version: 4
|
42
39
|
summary: file to clipboard
|
43
40
|
test_files: []
|