robbedoes
-
Posts
20 -
Joined
-
Last visited
Content Type
Blogs
Gallery
Downloads
Events
Profiles
Forums
Articles
Media Demo
Posts posted by robbedoes
-
-
On 1/15/2021 at 11:17 PM, deanishe said:
Thanks. I'm not getting the error you get. The workflow runs and opens the Google authentication page.
I'm sorry, I have absolutely no idea what the problem could be.
Which versions of Alfred and macOS are you running? Is it an M1 Mac?
What a shame, would love to use it. Thank you so much for looking into it anyway.
My laptop: MacBook Pro (13-inch, 2019)
MacOS: 11.1
Alfred: Alfred 4.3 (1205)
-
3 minutes ago, deanishe said:
Same thing. Open the google-drive.rb file from inside the workflow in your editor again and post the contents here please. Use the <> script button to post code. Remove your ID and secret after copying the text into the forum page. Don't change the file in your editor.
#!/usr/bin/ruby require 'socket' require 'net/http' require 'net/https' require 'uri' require 'json' require 'webrick' require 'shellwords' require 'fileutils' require 'logger' require 'tempfile' require 'open-uri' CLIENT_ID = 'xxxxxxx' CLIENT_SECRET = 'xxxxxxx' REDIRECT_URL = 'http://127.0.0.1:1337' RELEASES_URL = "https://api.github.com/repos/#{ENV['gh_repos'] || 'azai91/alfred-drive-workflow'}/releases" BUNDLE_ID = ENV['alfred_workflow_bundleid'] || 'com.drive.azai91' CACHE_DIR = ENV['alfred_workflow_cache'] || "/tmp/#{BUNDLE_ID}" WORKFLOW_NAME = ENV['alfred_workflow_name'] || 'Google Drive' VERSION = ENV['alfred_workflow_version'] || '1.0' EJECT_ICON_PATH = '/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/EjectMediaIcon.icns' SYNC_ICON_PATH = '/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/Sync.icns' MIME_TYPE_ICONS = { 'application/vnd.google-apps.document' => { :path => 'icons/doc.png' }, 'application/vnd.google-apps.spreadsheet' => { :path => 'icons/sheet.png' }, 'application/vnd.google-apps.presentation' => { :path => 'icons/slide.png' }, 'application/vnd.google-apps.form' => { :path => 'icons/form.png' }, 'application/pdf' => { :path => 'icons/dummy.pdf', :type => 'fileicon' }, } FileUtils.mkdir_p(CACHE_DIR) $log = Logger.new(STDERR.tty? || ENV['alfred_debug'] ? STDERR : "#{CACHE_DIR}/google-drive.log") $log.formatter = proc do |severity, datetime, progname, msg| "[#{datetime.strftime('%Y-%m-%d %H:%M:%S.%3N')}] [#{Process.pid}] %7s #{msg}\n" % "[#{severity}]" end def duration_in_words(from, to = Time.now) duration = (to - from).round.to_f case duration when 0...2 then 'just now' when 2...60 then '%.0f seconds ago' % duration when 60...90 then 'a minute ago' when 90...3570 then '%.0f minutes ago' % (duration/60) when 3570...5400 then 'an hour ago' when 5400...84600 then '%.0f hours ago' % (duration/(60*60)) when 84600...129600 then 'a day ago' when 129600...561600 then '%.0f days ago' % (duration/(24*60*60)) when 561600...1036800 then 'a week ago' when 1036800...2419200 then '%.0f weeks ago' % (duration/(7*24*60*60)) when 2419200...3952800 then 'a month ago' when 3952800...30304800 then '%.0f months ago' % (duration/(30.5*24*60*60)) when 30304800...47304000 then 'a year ago' else '%.0f years ago' % (duration/(365*24*60*60)) end end module SemVer def SemVer.compare(lhs, rhs) less?(lhs, rhs) ? -1 : (less?(rhs, lhs) ? +1 : 0) end def SemVer.less?(lhs_str, rhs_str) array_less?(parse(lhs_str), parse(rhs_str), true) do |a, b| array_less?(a.split('.'), b.split('.'), false) do |lhs, rhs| lhs_is_i, rhs_is_i = lhs =~ /^\d+$/, rhs =~ /^\d+$/ lhs_is_i && (!rhs_is_i || lhs.to_i < rhs.to_i) || !rhs_is_i && lhs < rhs end end end def SemVer.parse(str) if str =~ /\Av?(\d+(?:\.\d+){,2})(?:-(.+?))?(?:\+(.+))?\Z/ [ $1, $2 ].reject { |e| e.nil? } else $log.error("Not a valid version string: ‘#{str}’") [ '0' ] end end def SemVer.array_less?(lhs, rhs, longer_is_less) for i in 0...([lhs.size, rhs.size].min) return true if yield lhs[i], rhs[i] return false if yield rhs[i], lhs[i] end longer_is_less ? lhs.size > rhs.size : lhs.size < rhs.size end end class Keychain def self.add(password, account, service, comment = nil, label = nil) %x{ /usr/bin/security -q add-generic-password -j #{comment.to_s.shellescape} -l #{(label || service).shellescape} -s #{service.shellescape} -a #{account.shellescape} -w #{password.shellescape} -U } $log.error("Exit code #$? from /usr/bin/security while adding generic password for #{account}") if $?.exitstatus != 0 $?.exitstatus == 0 end def self.find(account, service) open("|/usr/bin/security 2>&1 find-generic-password -s #{service.shellescape} -a #{account.shellescape} -g") do |io| if io.read =~ /^password:\s+(?:0x(\h+)\s+)?"(.*)"$/ $1 ? $1.scan(/\h\h/).map { |ch| ch.hex }.pack("C*") : $2 end end end def self.comment(account, service) open("|/usr/bin/security 2>&1 -v find-generic-password -s #{service.shellescape} -a #{account.shellescape}") do |io| if io.read =~ /^attributes:\n(^\s+.*\n)+/m if $1 =~ /^\s+"icmt"<blob>=(?:0x(\h+)\s+)?"(.*)"$/ $1 ? $1.scan(/\h\h/).map { |ch| ch.hex }.pack('C*') : $2 end end end end def self.delete(account, service) %x{ /usr/bin/security 2>&1 -q delete-generic-password -s #{service.shellescape} -a #{account.shellescape} } $log.debug("Deleted generic password for #{account}") $?.exitstatus == 0 end end class Auth def initialize(host, port) @server = TCPServer.new(host, port) end def accept_token $log.debug("Starting HTTP server") loop do socket = @server.accept request = '' until (str = socket.gets).chomp.empty? request << str end util = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP) util.parse(StringIO.new(request)) $log.debug("Received HTTP request: #{util.request_line.chomp}") token = nil if util.request_method == 'GET' && util.path == '/' && util.query.has_key?('code') token = Auth.get_token({ 'grant_type' => 'authorization_code', 'code' => util.query['code'], 'redirect_uri' => REDIRECT_URL, }) end response = token.nil? ? "Something went wrong.\n" : "Successfully connected to Google Drive!\n" socket.print "HTTP/1.1 200 OK\r\n" socket.print "Content-Type: text/plain\r\n" socket.print "Content-Length: #{response.bytesize}\r\n" socket.print "Connection: close\r\n" socket.print "\r\n" socket.print response socket.close # Only serve a single request for ‘GET /’ break if util.request_method == 'GET' && util.path == '/' end $log.debug("Stopping HTTP server") end @service_name = "#{BUNDLE_ID}" def self.get_token(options) token = nil token_url = 'https://www.googleapis.com/oauth2/v3/token' response = Net::HTTP.post_form(URI.parse(token_url), options.merge({ 'client_id' => CLIENT_ID, 'client_secret' => CLIENT_SECRET, })) if response.code.to_i == 200 json = JSON.parse(response.body) if json.has_key?('refresh_token') Keychain.add(json['refresh_token'], 'drive_refresh_token', @service_name) $log.info("Refresh token added to keychain") end if json.has_key?('access_token') $log.warn("Access token has no expiration time") unless json.has_key?('expires_in') Keychain.add(json['access_token'], 'drive_access_token', @service_name, json.has_key?('expires_in') ? "Expires: #{(Time.now + json['expires_in'].to_i).iso8601}" : nil, @service_name) token = json['access_token'] $log.info("Access token added to keychain, expires in #{json['expires_in']} seconds") else $log.error("Access token missing from server response: #{json}") end else $log.error("Server returned #{response.code} when requesting token: #{JSON.parse(response.body)['error_description']}") end token end def self.token if Keychain.comment('drive_access_token', @service_name) =~ /^Expires: (.*)/ return Keychain.find('drive_access_token', @service_name) if (Time.parse($1) - Time.now) > 10 $log.info("Access token expired #{duration_in_words(Time.parse($1))}") end if refresh_token = Keychain.find('drive_refresh_token', @service_name) token = get_token({ 'grant_type' => 'refresh_token', 'refresh_token' => refresh_token, }) return token unless token.nil? end uri = URI.parse(REDIRECT_URL) thread = nil begin server = Auth.new(uri.host, uri.port) thread = Thread.new { server.accept_token } rescue Errno::EADDRINUSE => e $log.error("Already waiting for authentication: #{e}") end $log.info('Requesting user authentication via browser') auth_url = 'https://accounts.google.com/o/oauth2/auth?' + URI.encode_www_form({ 'client_id' => CLIENT_ID, 'redirect_uri' => REDIRECT_URL, 'scope' => 'https://www.googleapis.com/auth/drive', 'response_type' => 'code', 'access_type' => 'offline', 'approval_prompt' => 'force' }) %x{ /usr/bin/open ${open_args} #{auth_url.shellescape} } thread.join unless thread.nil? if token = Keychain.find('drive_access_token', @service_name) return token end abort 'No access token' end def self.revoke if token = Keychain.find('drive_refresh_token', @service_name) revoke_url = 'https://accounts.google.com/o/oauth2/revoke' response = Net::HTTP.post_form(URI.parse(revoke_url), { 'token' => token }) if response.code.to_i == 200 Keychain.delete('drive_access_token', @service_name) Keychain.delete('drive_refresh_token', @service_name) $log.info("Revoked access token") return true else $log.error("Server returned #{response.code} when revoking token: #{JSON.parse(response.body)['error_description']}") end else $log.warn("Refresh token missing while trying to revoke access") end false end end class GoogleDrive def self.get_items(token) uri = URI.parse('https://www.googleapis.com/drive/v2/files') mime_types = MIME_TYPE_ICONS.keys << 'application/vnd.google-apps.folder' query = "trashed=false and (#{mime_types.map { |type| "mimeType='#{type}'" }.join(' or ')})" if custom = ENV['custom_query'] query = "(#{query}) and (#{custom})" end parms = { 'q' => query, 'fields' => 'nextPageToken,items(id,title,alternateLink,mimeType,parents(id,isRoot),modifiedDate,lastModifyingUserName)', 'maxResults' => 1000, } http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true items = [] loop do uri.query = URI.encode_www_form(parms) request = Net::HTTP::Get.new(uri.request_uri) request.add_field('authorization', "Bearer #{token}") response = http.request(request) if response.code.to_i == 200 body = JSON.parse(response.body) $log.info("Got #{body['items'].size} items from Google Drive") items += body['items'] next if parms['pageToken'] = body['nextPageToken'] else $log.error("Server returned #{response.code} when fetching items from Google Drive: #{JSON.parse(response.body)['error_description']}") end break end items end end class Cache @cache_file = "#{CACHE_DIR}/drive-items.json" @cache_max_age = 10*60 # 10 minutes class << self attr_accessor :needs_update end def self.get_items items = { 'created' => DateTime.now, 'items' => GoogleDrive.get_items(Auth.token), } io = Tempfile.new('items', CACHE_DIR) io << JSON.generate(items) io.close FileUtils.mv(io.path, @cache_file) items end def self.items items = open(@cache_file) { |io| JSON.parse(io.read) } rescue nil if items.nil? items = get_items else $log.debug("Loaded #{items['items'].size} items from cache (created #{duration_in_words(Time.parse(items['created']))})") unless items.nil? @needs_update = (Time.now - Time.parse(items['created'])) > @cache_max_age end items['items'] end def self.delete File.unlink(@cache_file) rescue nil end end class Releases @cache_file = "#{CACHE_DIR}/github-releases.json" @cache_max_age = 12*60*60 # 12 hours def self.online res = open(RELEASES_URL) { |io| JSON.parse(io.read) } $log.info("Got #{res.size} releases from #{RELEASES_URL}") res.each do |release| release['assets'] = release['assets'].select do |asset| asset['name'] =~ /\.alfredworkflow$/ && asset['content_type'] == 'application/octet-stream' && asset['browser_download_url'] end end res.reject do |release| release['draft'] || release['assets'].empty? end end def self.cached releases = open(@cache_file, File::RDONLY) do |io| io.flock(File::LOCK_SH) JSON.parse(io.read) end rescue nil $log.debug("Loaded #{releases['releases'].size} releases from cache (created #{duration_in_words(Time.at(releases['created']))})") unless releases.nil? releases ? releases['releases'] : [ ] end def self.refresh_cache open(@cache_file, File::RDWR|File::CREAT) do |io| io.flock(File::LOCK_SH) releases = JSON.parse(io.read) rescue nil if releases.nil? || Time.now.to_f - releases['created'] > @cache_max_age releases = { 'created' => Time.now.to_f, 'releases' => Releases.online } io.flock(File::LOCK_EX) io.seek(0) io.truncate(0) io.write(JSON.generate(releases)) end end self end def self.latest(include_prereleases = false) releases = Releases.cached releases = releases.reject { |release| release['prerelease'] } unless include_prereleases releases.max { |lhs, rhs| SemVer.compare(lhs['tag_name'], rhs['tag_name']) } end end # ======== # = Main = # ======== start = Time.now $log.debug("#$0 #{ARGV}") begin if ARGV[0] == '--revoke' if Auth.revoke STDOUT << "Signed out of Google Drive\n" else STDERR << "Failed to sign out\n" end Cache.delete elsif ARGV[0] == '--create' type = ARGV[1] name = (ARGV[2] == '--name' ? ARGV[3] : nil).to_s.strip name = name.empty? ? 'Untitled' : name token = Auth.token uri = URI.parse('https://www.googleapis.com/drive/v3/files?fields=webViewLink') http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Post.new(uri.request_uri) request['authorization'] = "Bearer #{token}" request["content-type"] = 'application/json' request.body = JSON.generate({ :mimeType => type, :name => name, }) response = http.request(request) if response.code.to_i == 200 body = JSON.parse(response.body) if body.has_key?('webViewLink') %x{ /usr/bin/open ${open_args} #{body['webViewLink'].shellescape} } Cache.needs_update = true else $log.error("No webViewLink in response: #{response.body}") STDERR << "Link to new document missing from server response\n" end else $log.error("Server returned #{response.code} when creating document with type #{type}: #{JSON.parse(response.body)['error_description']}") STDERR << "Failed to create document.\nServer responded with status #{response.code}\n" end elsif ARGV[0] == '--filter' filter = ARGV[1].to_s.strip name = filter =~ /\S+\s(.+)/ ? $1 : nil res = [ [ 'Document', 'doc', '6EA9C89F-E56A-4DD5-AF21-870869D441E6' ], [ 'Spreadsheet', 'sheet', 'ACAA585E-C8CE-4D64-AE64-2AD41F6CA9F5' ], [ 'Presentation', 'slide', 'EB4B6437-13DB-4E65-9F7D-5BE060E37649' ], [ 'Form', 'form', '3D2966E3-0639-411D-8334-E1926B8626CF' ] ].map do |arr| { :title => "New #{arr[0]}", :subtitle => "Name: ‘#{name || 'Untitled'}’", :match => "New #{arr[0]} #{name}", :icon => { :path => "icons/#{arr[1]}.png" }, :variables => { :action => '--create', :name => name }, :arg => "application/vnd.google-apps.#{arr[0].downcase}", :uid => arr[2], } end res << { :title => 'Sign out of Google Drive', :icon => { :path => EJECT_ICON_PATH }, :variables => { :action => '--revoke' }, :arg => 'revoke', :uid => '87B64A2A-F6CE-461C-9A2F-303719D20EFE', } if latest = Releases.latest $log.debug("Latest online version is #{latest['tag_name']}, we are running #{VERSION}") if SemVer.less?(VERSION, latest['tag_name']) if asset = latest['assets'].first res << { :title => "Update to #{WORKFLOW_NAME} version #{latest['tag_name'].sub(/^v?/, '')}", :subtitle => "You are using version #{VERSION}", :icon => { :path => SYNC_ICON_PATH }, :uid => '1EBA5153-782F-485A-BC5A-10D2E5712DAC', :arg => asset['browser_download_url'], :variables => { :action => '--update-to' }, } end end end filter_regex = /#{filter.split(//).map { |ch| Regexp.escape(ch) }.join('.*?')}/i res = res.select { |item| (item[:match] || item[:title]) =~ filter_regex } if items = Cache.items parents_by_id = { } folders = items.select { |item| item['mimeType'] == 'application/vnd.google-apps.folder' } folders.each { |item| parents_by_id[item['id']] = item } files = items.reject { |item| item['mimeType'] == 'application/vnd.google-apps.folder' } files = files.select { |item| item['title'] =~ filter_regex } files = files.sort { |lhs, rhs| rhs['modifiedDate'] <=> lhs['modifiedDate'] } res += files.map do |item| parents = [] parent = item['parents'].first while parent && parent.has_key?('isRoot') && parent['isRoot'] == false if parent = parents_by_id[parent['id']] parents << parent['title'] parent = parent['parents'].first end end parents = parents.reverse.join('/') modified = "Modified #{duration_in_words(Time.parse(item['modifiedDate']))} by #{item['lastModifyingUserName'] || 'anonymous'}" { :uid => item['id'], :title => item['title'], :subtitle => [ parents, modified ].reject { |e| e.to_s.empty? }.join(' • '), :icon => MIME_TYPE_ICONS[item['mimeType']], :arg => item['alternateLink'], :variables => { :action => '--open' }, } end end container = { :items => res } container[:rerun] = 0.8 if Cache.needs_update STDOUT << JSON.generate(container) elsif ARGV[0] == '--check-for-updates' if latest = Releases.refresh_cache.latest $log.debug("Latest online version is #{latest['tag_name']}, we are running #{VERSION}") if SemVer.less?(VERSION, latest['tag_name']) STDOUT << "Enter ‘d update’ to install #{WORKFLOW_NAME} version #{latest['tag_name'].sub(/^v?/, '')}\nYou are using version #{VERSION}\n" end end elsif ARGV[0] == '--update-to' url = ARGV[1] path = File.join(ENV['TMPDIR'] || '/tmp', File.basename(URI.parse(url).path)) $log.info("Downloading #{url} to #{path}") %x{ /usr/bin/curl -Lo #{path.shellescape} #{url.shellescape} } if $?.exitstatus == 0 $log.info("Installing update using ‘open’") %x{ /usr/bin/open #{path.shellescape} } else $log.error("Exit code #$? from /usr/bin/curl") end else $log.error("Unknown argument") end rescue Exception => e $log.fatal("Uncaught exception: #{e}") e.backtrace.each { |line| $log.fatal(line) } end $log.debug("Execution took #{(Time.now - start).round(3)} seconds") if Cache.needs_update Process.daemon begin open("#{CACHE_DIR}/lockfile", File::RDWR|File::CREAT) do |io| if io.flock(File::LOCK_EX|File::LOCK_NB) $log.debug("Start background cache update") Cache.get_items $log.debug("Finished background cache update") else $log.debug('Skip background cache update: Another process is already running') end end rescue Exception => e $log.fatal("Uncaught exception: #{e}") e.backtrace.each { |line| $log.fatal(line) } end end
-
2 minutes ago, deanishe said:
Can you try searching for a normal word instead of a symbol?
-
3 minutes ago, deanishe said:
And what happens then? The error above is because the ID and secret are nonsense. What happens if you use the real ones?
this happens with the real ones:
-
1 minute ago, Mr Pennyworth said:
Whoops! My bad
Meant Alfred's debugger.This is great!!
The browser tab for authentication opens.
There are no "command not found" errors in the debugger anymore.
If you do the exact same thing as in the video, but with the actual secret and ID. I want to say "it'll work!". However, you've repeatedly told us that it didn't
Sadly, I've got nothing further to help but to say, "try one last time exactly like in video, but with the id and the secret"I already tried the id and secret, several times 😥 Too bad. Do you know some place or someone I could ask besides this forum? Thanks a lot for your help anyway!!
-
14 minutes ago, deanishe said:
I think he means Alfred's debugger. I don't think there's anything relevant in Console.app.
At any rate, you need to also run the workflow so we can see the actual error.
https://www.loom.com/share/041178f48aee46b5a4888745881cdaca 🥺
-
2 hours ago, Mr Pennyworth said:
Same thing for me!
Here's a last-ditch effort:
I loved how you shared that video! It was very helpful
Based on the back-and-forth so far, I feel like we'd be able to spot what's going wrong if you could share an entire video of the download-edit-save-run process with console open.
To make sure that you're not sharing the client secret in the video, in the "edit" phase, just add 'xxxxxxxxxxxxx' to the relevant lines in the rb file instead of actual secret. (by the way, I should clarify that it is perfectly fine to share the app id and client secret from the perspective of your security. from the perspective of quotas, maybe not so)
Thanks for your help. Here is the video. Didn't really understand what you meant with console open, so if I've missed it, I'm sorry.
-
Just now, deanishe said:
This is not nothing.Right, so Ruby’s working. Based on the debugger output, you’ve broken the workflow and now it’s trying to run the Ruby script with bash or zsh.
I don’t know what you changed, but it was something you weren’t supposed to. Please re-install the original workflow again, and then only change the ID and secret. Nothing else.
I've deleted, re-installed and only changed the ID and secret around 10 times by now.
It's the only thing I changed and it's still not working. I really wonder what I'm doing, getting kind of tired of it right now. I guess you guys are too, but you're my only hope.
-
8 minutes ago, deanishe said:
What is the output if you run the command /usr/bin/ruby --version in Terminal?
Nothing:
MacBook-Pro-van-Robin:~ robin$ /usr/bin/ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [universal.x86_64-darwin20]
-
3 minutes ago, Mr Pennyworth said:
Can you confirm that this is the first line of google-drive.rb file?
#!/usr/bin/rubyYes, this is the first line.
-
3 minutes ago, robbedoes said:I've tried to paste a screenshot of the code, but the image is too big or something.MIME_TYPE_ICONS = {'application/vnd.google-apps.document' => { :path => 'icons/doc.png' },'application/vnd.google-apps.spreadsheet' => { :path => 'icons/sheet.png' },'application/vnd.google-apps.presentation' => { :path => 'icons/slide.png' },'application/vnd.google-apps.form' => { :path => 'icons/form.png' },'application/pdf' => { :path => 'icons/dummy.pdf', :type => 'fileicon' },}This is line 27 - 33
-
5 minutes ago, Mr Pennyworth said:
As @deanishe has pointed out,
This isn't how it should be done.
Try the following:
- Download the workflow (the .alfredworkflow file): https://github.com/azai91/alfred-drive-workflow/releases/download/v2.1/Google-Drive.alfredworkflow
- Double click on the downloaded file to open it with alfred
- Right click on the workflow inside Alfred, and select "Open in Finder"
- In the finder window that's opened in (3), edit the google-drive.rb file with your own credentials.
- Save the file
- Run the workflow
Thanks, I've followed your steps and tried to run the workflow. Does not work.
Tried debugging inside Alfred:
-
13 minutes ago, deanishe said:
This:
You've zipped it incorrectly. Don’t zip the folder the workflow is in, select the contents of the folder and zip them.
Or better yet, download and install the original workflow and edit it after installation. -
13 hours ago, deanishe said:
We can't help you if you can't give us a better description of what's going wrong than that.
There is no better description than what I've already mentioned. You said to get the credentials and edit the workflow. I got the credentials en edited the workflow, but it still isn't working. You also didn't give more instructions than that. It's very difficult if you don't have experience in this type of thing.
Steps I've made:
1. get the workflow
2. get the credentials
3. edit the workflow
4. zip the edited workflow and gave it the name 'archive.alfredworkflow'
5. double click > will not install
6. tried to drag the workflow in alfred > will not install
-
8 hours ago, deanishe said:
Right, so you're having the same problem as everyone above. Here are Google's instructions for setting up a Google application:
https://developers.google.com/identity/protocols/oauth2/
I explained where to put the ID and secret when you've got them further up the page.
Thanks, I've already tried this. I got the credentials, I changed the code. Still not working.
-
-
Hi, please help. I've been struggling for over an hour trying to login, but I get an error that Alfred is blocked by Google.
I've read all the threads there are about this issue, but it's all blablabla for me. I'm not a coder, so I don't know anything about API's and stuff. How can I get this to work please? I just want to search my Google files 😞
Thank you.
Google Drive
in Share your Workflows
Posted
I've found an alternative Google Drive workflow which I installed with help of a friend of mine. This one works!