star-dlp 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0f53a06472e77562560428a8f65cce8a670f26d0d430cd2c947dc8843cd5c697
4
+ data.tar.gz: 5e9ca4e9a2fc62a854fc1f8b2f8473c5379f8e03c6f8772e9f4eeb1055cbc4dd
5
+ SHA512:
6
+ metadata.gz: 290b940570744dc5ed74fbdfeda58794312109c5412a7751b15d40d3eb857418c676414e0ee692a1db31bd2458ff566e1e4437a41786102386563df065cd4b59
7
+ data.tar.gz: f186110f59e875bd99aa586544ad8a2d69243172d45ee4b76bfa458e373808d9dd6ee8fa9e952251c06e9264a009bf6b3408556334445ed88c4cd4dded8ac8c5
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1 @@
1
+ inherit_from: .rubocop_todo.yml
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,118 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2025-03-16 07:02:31 UTC using RuboCop version 1.74.0.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 1
10
+ # This cop supports safe autocorrection (--autocorrect).
11
+ # Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation, Include.
12
+ # Include: **/*.gemspec
13
+ Gemspec/OrderedDependencies:
14
+ Exclude:
15
+ - 'star-dlp.gemspec'
16
+
17
+ # Offense count: 4
18
+ # This cop supports safe autocorrection (--autocorrect).
19
+ # Configuration parameters: EnforcedStyle.
20
+ # SupportedStyles: final_newline, final_blank_line
21
+ Layout/TrailingEmptyLines:
22
+ Exclude:
23
+ - 'exe/star-dlp'
24
+ - 'lib/star/dlp/cli.rb'
25
+ - 'lib/star/dlp/config.rb'
26
+ - 'lib/star/dlp/downloader.rb'
27
+
28
+ # Offense count: 67
29
+ # This cop supports safe autocorrection (--autocorrect).
30
+ # Configuration parameters: AllowInHeredoc.
31
+ Layout/TrailingWhitespace:
32
+ Exclude:
33
+ - 'exe/star-dlp'
34
+ - 'lib/star/dlp.rb'
35
+ - 'lib/star/dlp/cli.rb'
36
+ - 'lib/star/dlp/config.rb'
37
+ - 'lib/star/dlp/downloader.rb'
38
+
39
+ # Offense count: 4
40
+ # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
41
+ Metrics/AbcSize:
42
+ Max: 80
43
+
44
+ # Offense count: 1
45
+ # Configuration parameters: CountComments, CountAsOne.
46
+ Metrics/ClassLength:
47
+ Max: 144
48
+
49
+ # Offense count: 1
50
+ # Configuration parameters: AllowedMethods, AllowedPatterns.
51
+ Metrics/CyclomaticComplexity:
52
+ Max: 22
53
+
54
+ # Offense count: 3
55
+ # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
56
+ Metrics/MethodLength:
57
+ Max: 81
58
+
59
+ # Offense count: 1
60
+ # Configuration parameters: AllowedMethods, AllowedPatterns.
61
+ Metrics/PerceivedComplexity:
62
+ Max: 27
63
+
64
+ # Offense count: 2
65
+ Naming/AccessorMethodName:
66
+ Exclude:
67
+ - 'lib/star/dlp/downloader.rb'
68
+
69
+ # Offense count: 4
70
+ # Configuration parameters: AllowedConstants.
71
+ Style/Documentation:
72
+ Exclude:
73
+ - 'spec/**/*'
74
+ - 'test/**/*'
75
+ - 'lib/star/dlp.rb'
76
+ - 'lib/star/dlp/cli.rb'
77
+ - 'lib/star/dlp/config.rb'
78
+ - 'lib/star/dlp/downloader.rb'
79
+
80
+ # Offense count: 1
81
+ # This cop supports safe autocorrection (--autocorrect).
82
+ Style/IfUnlessModifier:
83
+ Exclude:
84
+ - 'lib/star/dlp/downloader.rb'
85
+
86
+ # Offense count: 1
87
+ # This cop supports safe autocorrection (--autocorrect).
88
+ # Configuration parameters: EnforcedStyle, MinBodyLength.
89
+ # SupportedStyles: skip_modifier_ifs, always
90
+ Style/Next:
91
+ Exclude:
92
+ - 'lib/star/dlp/downloader.rb'
93
+
94
+ # Offense count: 90
95
+ # This cop supports safe autocorrection (--autocorrect).
96
+ # Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline.
97
+ # SupportedStyles: single_quotes, double_quotes
98
+ Style/StringLiterals:
99
+ Exclude:
100
+ - 'Gemfile'
101
+ - 'Rakefile'
102
+ - 'bin/console'
103
+ - 'exe/star-dlp'
104
+ - 'lib/star/dlp.rb'
105
+ - 'lib/star/dlp/cli.rb'
106
+ - 'lib/star/dlp/config.rb'
107
+ - 'lib/star/dlp/downloader.rb'
108
+ - 'lib/star/dlp/version.rb'
109
+ - 'spec/spec_helper.rb'
110
+ - 'spec/star/dlp_spec.rb'
111
+ - 'star-dlp.gemspec'
112
+
113
+ # Offense count: 1
114
+ # This cop supports safe autocorrection (--autocorrect).
115
+ # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings.
116
+ # URISchemes: http, https
117
+ Layout/LineLength:
118
+ Max: 125
@@ -0,0 +1,20 @@
1
+ {
2
+ "version": "0.2.0",
3
+ "configurations": [
4
+ {
5
+ "type": "rdbg",
6
+ "name": "Debug with rdbg",
7
+ "request": "launch",
8
+ "script": "exe/star-dlp",
9
+ "command": "bundle exec",
10
+ "useTerminal": true,
11
+ "args": ["download", "lululau"],
12
+ "askParameters": false
13
+ },
14
+ {
15
+ "type": "rdbg",
16
+ "name": "Attach with rdbg",
17
+ "request": "attach"
18
+ }
19
+ ]
20
+ }
@@ -0,0 +1,84 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6
+
7
+ We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8
+
9
+ ## Our Standards
10
+
11
+ Examples of behavior that contributes to a positive environment for our community include:
12
+
13
+ * Demonstrating empathy and kindness toward other people
14
+ * Being respectful of differing opinions, viewpoints, and experiences
15
+ * Giving and gracefully accepting constructive feedback
16
+ * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17
+ * Focusing on what is best not just for us as individuals, but for the overall community
18
+
19
+ Examples of unacceptable behavior include:
20
+
21
+ * The use of sexualized language or imagery, and sexual attention or
22
+ advances of any kind
23
+ * Trolling, insulting or derogatory comments, and personal or political attacks
24
+ * Public or private harassment
25
+ * Publishing others' private information, such as a physical or email
26
+ address, without their explicit permission
27
+ * Other conduct which could reasonably be considered inappropriate in a
28
+ professional setting
29
+
30
+ ## Enforcement Responsibilities
31
+
32
+ Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
33
+
34
+ Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
35
+
36
+ ## Scope
37
+
38
+ This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
39
+
40
+ ## Enforcement
41
+
42
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at liuxiang921@gmail.com. All complaints will be reviewed and investigated promptly and fairly.
43
+
44
+ All community leaders are obligated to respect the privacy and security of the reporter of any incident.
45
+
46
+ ## Enforcement Guidelines
47
+
48
+ Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
49
+
50
+ ### 1. Correction
51
+
52
+ **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
53
+
54
+ **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
55
+
56
+ ### 2. Warning
57
+
58
+ **Community Impact**: A violation through a single incident or series of actions.
59
+
60
+ **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
61
+
62
+ ### 3. Temporary Ban
63
+
64
+ **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
65
+
66
+ **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
67
+
68
+ ### 4. Permanent Ban
69
+
70
+ **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
71
+
72
+ **Consequence**: A permanent ban from any sort of public interaction within the community.
73
+
74
+ ## Attribution
75
+
76
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
77
+ available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
78
+
79
+ Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
80
+
81
+ [homepage]: https://www.contributor-covenant.org
82
+
83
+ For answers to common questions about this code of conduct, see the FAQ at
84
+ https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in star-dlp.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "rspec", "~> 3.0"
11
+
12
+ gem "github_api", "~> 0.19.0"
data/Gemfile.lock ADDED
@@ -0,0 +1,94 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ star-dlp (0.1.0)
5
+ fileutils (~> 1.6)
6
+ github_api (~> 0.19.0)
7
+ json (~> 2.6)
8
+ thor (~> 1.2)
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ addressable (2.8.7)
14
+ public_suffix (>= 2.0.2, < 7.0)
15
+ base64 (0.2.0)
16
+ bigdecimal (3.1.9)
17
+ descendants_tracker (0.0.4)
18
+ thread_safe (~> 0.3, >= 0.3.1)
19
+ diff-lcs (1.5.0)
20
+ faraday (1.10.4)
21
+ faraday-em_http (~> 1.0)
22
+ faraday-em_synchrony (~> 1.0)
23
+ faraday-excon (~> 1.1)
24
+ faraday-httpclient (~> 1.0)
25
+ faraday-multipart (~> 1.0)
26
+ faraday-net_http (~> 1.0)
27
+ faraday-net_http_persistent (~> 1.0)
28
+ faraday-patron (~> 1.0)
29
+ faraday-rack (~> 1.0)
30
+ faraday-retry (~> 1.0)
31
+ ruby2_keywords (>= 0.0.4)
32
+ faraday-em_http (1.0.0)
33
+ faraday-em_synchrony (1.0.0)
34
+ faraday-excon (1.1.0)
35
+ faraday-httpclient (1.0.1)
36
+ faraday-multipart (1.1.0)
37
+ multipart-post (~> 2.0)
38
+ faraday-net_http (1.0.2)
39
+ faraday-net_http_persistent (1.2.0)
40
+ faraday-patron (1.0.0)
41
+ faraday-rack (1.0.0)
42
+ faraday-retry (1.0.3)
43
+ fileutils (1.7.0)
44
+ github_api (0.19.0)
45
+ addressable (~> 2.4)
46
+ descendants_tracker (~> 0.0.4)
47
+ faraday (>= 0.8, < 2)
48
+ hashie (~> 3.5, >= 3.5.2)
49
+ oauth2 (~> 1.0)
50
+ hashie (3.6.0)
51
+ json (2.6.3)
52
+ jwt (2.10.1)
53
+ base64
54
+ multi_json (1.15.0)
55
+ multi_xml (0.7.1)
56
+ bigdecimal (~> 3.1)
57
+ multipart-post (2.4.1)
58
+ oauth2 (1.4.11)
59
+ faraday (>= 0.17.3, < 3.0)
60
+ jwt (>= 1.0, < 3.0)
61
+ multi_json (~> 1.3)
62
+ multi_xml (~> 0.5)
63
+ rack (>= 1.2, < 4)
64
+ public_suffix (6.0.1)
65
+ rack (3.1.12)
66
+ rake (13.2.1)
67
+ rspec (3.12.0)
68
+ rspec-core (~> 3.12.0)
69
+ rspec-expectations (~> 3.12.0)
70
+ rspec-mocks (~> 3.12.0)
71
+ rspec-core (3.12.2)
72
+ rspec-support (~> 3.12.0)
73
+ rspec-expectations (3.12.3)
74
+ diff-lcs (>= 1.2.0, < 2.0)
75
+ rspec-support (~> 3.12.0)
76
+ rspec-mocks (3.12.6)
77
+ diff-lcs (>= 1.2.0, < 2.0)
78
+ rspec-support (~> 3.12.0)
79
+ rspec-support (3.12.1)
80
+ ruby2_keywords (0.0.5)
81
+ thor (1.3.1)
82
+ thread_safe (0.3.6)
83
+
84
+ PLATFORMS
85
+ arm64-darwin-23
86
+
87
+ DEPENDENCIES
88
+ github_api (~> 0.19.0)
89
+ rake (~> 13.0)
90
+ rspec (~> 3.0)
91
+ star-dlp!
92
+
93
+ BUNDLED WITH
94
+ 2.4.12
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Liu Xiang
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # Star-DLP
2
+
3
+ Star-DLP (Star Downloader) is a Ruby gem for downloading and managing repositories you've starred on GitHub. It supports downloading starred repositories as JSON and Markdown files, and features incremental downloading, only downloading new starred repositories that aren't already in your local collection.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'star-dlp'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ ```bash
16
+ $ bundle install
17
+ ```
18
+
19
+ Or install it yourself as:
20
+
21
+ ```bash
22
+ $ gem install star-dlp
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ ### Configuration
28
+
29
+ First, you can configure Star-DLP:
30
+
31
+ ```bash
32
+ $ star-dlp config --token=your_github_token
33
+ ```
34
+
35
+ You can set the following options:
36
+ - `--token`: GitHub API token (recommended to avoid API rate limits)
37
+ - `--output_dir`: Output directory
38
+ - `--json_dir`: JSON files directory
39
+ - `--markdown_dir`: Markdown files directory
40
+
41
+ ### Downloading Starred Repositories
42
+
43
+ To download your starred repositories:
44
+
45
+ ```bash
46
+ $ star-dlp download your_github_username
47
+ ```
48
+
49
+ This will download all your starred repositories and save them as JSON and Markdown files. If you've previously downloaded some repositories, it will only download newly starred repositories.
50
+
51
+ ### View Version
52
+
53
+ ```bash
54
+ $ star-dlp version
55
+ ```
56
+
57
+ ## File Structure
58
+
59
+ Star-DLP saves files in the following locations:
60
+
61
+ - Configuration file: `~/.star-dlp/config.json`
62
+ - Starred repositories: `~/.star-dlp/stars/`
63
+ - JSON files: `~/.star-dlp/stars/json/`
64
+ - Markdown files: `~/.star-dlp/stars/markdown/`
65
+
66
+ ## Development
67
+
68
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
69
+
70
+ To install this gem onto your local machine, run `bundle exec rake install`.
71
+
72
+ ## Contributing
73
+
74
+ Bug reports and pull requests are welcome. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
75
+
76
+ ## License
77
+
78
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/README_zh.md ADDED
@@ -0,0 +1,78 @@
1
+ # Star-DLP
2
+
3
+ Star-DLP (Star Downloader) 是一个 Ruby gem,用于下载和管理您在 GitHub 上标星的仓库。它支持将星标仓库下载为 JSON 和 Markdown 文件,并且支持增量下载,只下载本地没有的新星标仓库。
4
+
5
+ ## 安装
6
+
7
+ 添加这一行到您的应用程序的 Gemfile:
8
+
9
+ ```ruby
10
+ gem 'star-dlp'
11
+ ```
12
+
13
+ 然后执行:
14
+
15
+ ```bash
16
+ $ bundle install
17
+ ```
18
+
19
+ 或者自己安装:
20
+
21
+ ```bash
22
+ $ gem install star-dlp
23
+ ```
24
+
25
+ ## 使用方法
26
+
27
+ ### 配置
28
+
29
+ 首先,您可以配置 Star-DLP:
30
+
31
+ ```bash
32
+ $ star-dlp config --token=your_github_token
33
+ ```
34
+
35
+ 您可以设置以下选项:
36
+ - `--token`: GitHub API 令牌 (推荐使用,以避免 API 速率限制)
37
+ - `--output_dir`: 输出目录
38
+ - `--json_dir`: JSON 文件目录
39
+ - `--markdown_dir`: Markdown 文件目录
40
+
41
+ ### 下载星标仓库
42
+
43
+ 要下载您的星标仓库:
44
+
45
+ ```bash
46
+ $ star-dlp download your_github_username
47
+ ```
48
+
49
+ 这将下载您所有的星标仓库,并将它们保存为 JSON 和 Markdown 文件。如果您之前已经下载过一些仓库,它只会下载新的星标仓库。
50
+
51
+ ### 查看版本
52
+
53
+ ```bash
54
+ $ star-dlp version
55
+ ```
56
+
57
+ ## 文件结构
58
+
59
+ Star-DLP 将文件保存在以下位置:
60
+
61
+ - 配置文件: `~/.star-dlp/config.json`
62
+ - 星标仓库: `~/.star-dlp/stars/`
63
+ - JSON 文件: `~/.star-dlp/stars/json/`
64
+ - Markdown 文件: `~/.star-dlp/stars/markdown/`
65
+
66
+ ## 开发
67
+
68
+ 克隆仓库后,运行 `bin/setup` 安装依赖项。然后,运行 `rake spec` 运行测试。您也可以运行 `bin/console` 进入交互式提示符,允许您进行实验。
69
+
70
+ 要安装此 gem 到您的本地机器,运行 `bundle exec rake install`。
71
+
72
+ ## 贡献
73
+
74
+ 欢迎 Bug 报告和拉取请求。本项目旨在成为一个安全、友好的协作空间,贡献者需要遵守行为准则。
75
+
76
+ ## 许可证
77
+
78
+ 该 gem 可在 [MIT 许可证](https://opensource.org/licenses/MIT)下使用。
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
data/exe/star-dlp ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "star/dlp"
5
+
6
+ Star::Dlp.start(ARGV)
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+ require_relative "config"
5
+ require_relative "downloader"
6
+
7
+ module Star
8
+ module Dlp
9
+ class CLI < Thor
10
+ desc "download USERNAME", "Download GitHub stars for a user"
11
+ option :token, type: :string, desc: "GitHub API token"
12
+ option :output_dir, type: :string, desc: "Output directory for stars"
13
+ option :json_dir, type: :string, desc: "Directory for JSON files"
14
+ option :markdown_dir, type: :string, desc: "Directory for Markdown files"
15
+ def download(username)
16
+ config = Config.load
17
+
18
+ # Override config with command line options
19
+ config.github_token = options[:token] if options[:token]
20
+ config.output_dir = options[:output_dir] if options[:output_dir]
21
+ config.json_dir = options[:json_dir] if options[:json_dir]
22
+ config.markdown_dir = options[:markdown_dir] if options[:markdown_dir]
23
+
24
+ # Save config for future use
25
+ config.save
26
+
27
+ downloader = Downloader.new(config, username)
28
+ downloader.download
29
+ end
30
+
31
+ desc "config", "Configure star-dlp"
32
+ option :token, type: :string, desc: "GitHub API token"
33
+ option :output_dir, type: :string, desc: "Output directory for stars"
34
+ option :json_dir, type: :string, desc: "Directory for JSON files"
35
+ option :markdown_dir, type: :string, desc: "Directory for Markdown files"
36
+ def config
37
+ config = Config.load
38
+
39
+ # Override config with command line options
40
+ config.github_token = options[:token] if options[:token]
41
+ config.output_dir = options[:output_dir] if options[:output_dir]
42
+ config.json_dir = options[:json_dir] if options[:json_dir]
43
+ config.markdown_dir = options[:markdown_dir] if options[:markdown_dir]
44
+
45
+ # Save config for future use
46
+ config.save
47
+
48
+ puts "Configuration saved successfully!"
49
+ puts "GitHub Token: #{config.github_token || 'Not set'}"
50
+ puts "Output Directory: #{config.output_dir}"
51
+ puts "JSON Directory: #{config.json_dir}"
52
+ puts "Markdown Directory: #{config.markdown_dir}"
53
+ end
54
+
55
+ desc "version", "Show version"
56
+ def version
57
+ puts "star-dlp version #{VERSION}"
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "json"
5
+
6
+ module Star
7
+ module Dlp
8
+ class Config
9
+ DEFAULT_CONFIG_DIR = File.join(Dir.home, ".star-dlp")
10
+ DEFAULT_CONFIG_FILE = File.join(DEFAULT_CONFIG_DIR, "config.json")
11
+ DEFAULT_STARS_DIR = File.join(DEFAULT_CONFIG_DIR, "stars")
12
+ DEFAULT_JSON_DIR = File.join(DEFAULT_STARS_DIR, "json")
13
+ DEFAULT_MARKDOWN_DIR = File.join(DEFAULT_STARS_DIR, "markdown")
14
+
15
+ attr_accessor :github_token, :output_dir, :json_dir, :markdown_dir
16
+
17
+ def initialize(options = {})
18
+ @github_token = options[:github_token]
19
+ @output_dir = options[:output_dir] || DEFAULT_STARS_DIR
20
+ @json_dir = options[:json_dir] || DEFAULT_JSON_DIR
21
+ @markdown_dir = options[:markdown_dir] || DEFAULT_MARKDOWN_DIR
22
+
23
+ create_directories
24
+ end
25
+
26
+ def self.load
27
+ return new unless File.exist?(DEFAULT_CONFIG_FILE)
28
+
29
+ config_data = JSON.parse(File.read(DEFAULT_CONFIG_FILE), symbolize_names: true)
30
+ new(config_data)
31
+ end
32
+
33
+ def save
34
+ FileUtils.mkdir_p(DEFAULT_CONFIG_DIR) unless Dir.exist?(DEFAULT_CONFIG_DIR)
35
+
36
+ config_data = {
37
+ github_token: @github_token,
38
+ output_dir: @output_dir,
39
+ json_dir: @json_dir,
40
+ markdown_dir: @markdown_dir
41
+ }
42
+
43
+ File.write(DEFAULT_CONFIG_FILE, JSON.pretty_generate(config_data))
44
+ end
45
+
46
+ private
47
+
48
+ def create_directories
49
+ [@output_dir, @json_dir, @markdown_dir].each do |dir|
50
+ FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,359 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "github_api"
4
+ require "json"
5
+ require "fileutils"
6
+ require "time"
7
+ require "base64"
8
+
9
+ module Star
10
+ module Dlp
11
+ class Downloader
12
+ attr_reader :config, :github, :username
13
+
14
+ LAST_REPO_FILE = "last_downloaded_repo.txt"
15
+
16
+ def initialize(config, username)
17
+ @config = config
18
+ @username = username
19
+
20
+ # Initialize GitHub API client with the special Accept header for starred_at field
21
+ options = {
22
+ headers: {
23
+ "Accept" => "application/vnd.github.star+json",
24
+ "X-GitHub-Api-Version" => "2022-11-28"
25
+ }
26
+ }
27
+
28
+ # Add token if available
29
+ options[:oauth_token] = config.github_token if config.github_token
30
+
31
+ @github = Github.new(options)
32
+ end
33
+
34
+ def download
35
+ puts "Downloading stars for user: #{username}"
36
+ # Get last downloaded info
37
+ last_repo_name = get_last_repo_name
38
+
39
+ if last_repo_name
40
+ puts "Last download stopped at repository: #{last_repo_name}."
41
+ puts "Will only fetch stars added after this timestamp."
42
+ else
43
+ puts "No previous download record found. Will download all stars."
44
+ end
45
+
46
+ # Download all stars
47
+ all_stars = []
48
+ page = 1
49
+ newest_repo_name = nil
50
+ newest_starred_at = nil
51
+
52
+ # Download stars page by page
53
+ loop do
54
+ puts "Fetching page #{page}..."
55
+ stars = github.activity.starring.starred(user: username, per_page: 100, page: page)
56
+ break if stars.empty?
57
+
58
+ puts " - Got #{stars.size} repositories from page #{page}"
59
+
60
+ # Store the name and starred_at of the newest star (first star on first page)
61
+ if page == 1 && !stars.empty?
62
+ newest_repo = stars.first
63
+ newest_repo_name = get_repo_full_name(newest_repo)
64
+ newest_starred_at = newest_repo.respond_to?(:starred_at) ? newest_repo.starred_at : nil
65
+
66
+ puts "Newest starred repository: #{newest_repo_name} (starred at: #{newest_starred_at || 'unknown'})"
67
+ end
68
+
69
+ # Check if we've reached repos that were already downloaded
70
+ should_break = false
71
+
72
+ # If we have both last_repo_name, we can use them for comparison
73
+ if last_repo_name
74
+ stars.each do |star|
75
+ repo_name = get_repo_full_name(star)
76
+ starred_at = star.respond_to?(:starred_at) ? star.starred_at : nil
77
+
78
+ # If we find a star with the same name and timestamp, we've reached our previous download point
79
+ if repo_name == last_repo_name
80
+ puts " - Reached previously downloaded repository: #{repo_name} (starred at: #{starred_at})"
81
+ puts " - Stopping pagination."
82
+ should_break = true
83
+ break
84
+ end
85
+ all_stars << star
86
+ end
87
+ else
88
+ all_stars.concat(stars)
89
+ end
90
+
91
+ page += 1
92
+
93
+ break if should_break
94
+ end
95
+
96
+ # Filter out stars that already exist in our collection
97
+ new_stars = all_stars
98
+
99
+ puts "Found #{new_stars.size} new starred repositories to download"
100
+
101
+ # Save new stars
102
+ if new_stars.any?
103
+ puts "Downloading new repositories:"
104
+ new_stars.each_with_index do |star, index|
105
+ puts " [#{index + 1}/#{new_stars.size}] Downloading: #{get_repo_full_name(star)}"
106
+ save_star_as_json(star)
107
+ save_star_as_markdown(star)
108
+ end
109
+
110
+ puts "Download completed successfully!"
111
+ else
112
+ puts "No new repositories to download."
113
+ end
114
+
115
+ # Save the newest repo info for next time
116
+ if newest_repo_name
117
+ save_last_repo_name(newest_repo_name)
118
+ puts "Saved latest repository name: #{newest_repo_name}"
119
+ end
120
+ end
121
+
122
+ private
123
+
124
+ def get_last_repo_name
125
+ last_repo_file = File.join(config.output_dir, LAST_REPO_FILE)
126
+ return nil unless File.exist?(last_repo_file)
127
+
128
+ File.read(last_repo_file).strip
129
+ end
130
+
131
+ def save_last_repo_name(repo_name)
132
+ last_repo_file = File.join(config.output_dir, LAST_REPO_FILE)
133
+ File.write(last_repo_file, repo_name)
134
+ end
135
+
136
+
137
+ def save_star_as_json(star)
138
+ star_data = star.to_hash
139
+
140
+ # Get starred_at date or use current date as fallback
141
+ starred_at = star.respond_to?(:starred_at) ? Time.parse(star.starred_at) : Time.now
142
+
143
+ # Create directory structure based on starred_at date: json/YYYY/MM/
144
+ year_dir = starred_at.strftime("%Y")
145
+ month_dir = starred_at.strftime("%m")
146
+ target_dir = File.join(config.json_dir, year_dir, month_dir)
147
+ FileUtils.mkdir_p(target_dir) unless Dir.exist?(target_dir)
148
+
149
+ # Format filename: YYYYMMDD.username.repo_name.json
150
+ date_str = starred_at.strftime("%Y%m%d")
151
+ repo_name = get_repo_full_name(star).gsub('/', '.')
152
+ filename = "#{date_str}.#{repo_name}.json"
153
+
154
+ filepath = File.join(target_dir, filename)
155
+ File.write(filepath, JSON.pretty_generate(star_data))
156
+ end
157
+
158
+ def save_star_as_markdown(star)
159
+ # Get starred_at date or use current date as fallback
160
+ starred_at = star.respond_to?(:starred_at) ? Time.parse(star.starred_at) : Time.now
161
+
162
+ # Create directory structure based on starred_at date: markdown/YYYY/MM/
163
+ year_dir = starred_at.strftime("%Y")
164
+ month_dir = starred_at.strftime("%m")
165
+ target_dir = File.join(config.markdown_dir, year_dir, month_dir)
166
+ FileUtils.mkdir_p(target_dir) unless Dir.exist?(target_dir)
167
+
168
+ # Format filename: YYYYMMDD.username.repo_name.md
169
+ date_str = starred_at.strftime("%Y%m%d")
170
+ repo_full_name = get_repo_full_name(star)
171
+ repo_name = repo_full_name.gsub('/', '.')
172
+ filename = "#{date_str}.#{repo_name}.md"
173
+
174
+ filepath = File.join(target_dir, filename)
175
+
176
+ # Include starred_at in the markdown
177
+ starred_at_str = star.respond_to?(:starred_at) ? star.starred_at : "N/A"
178
+
179
+ # Basic repository information
180
+ content = <<~MARKDOWN
181
+ # #{repo_full_name}
182
+
183
+ #{get_description(star)}
184
+
185
+ - **Stars**: #{get_stargazers_count(star)}
186
+ - **Forks**: #{get_forks_count(star)}
187
+ - **Language**: #{get_language(star)}
188
+ - **Created at**: #{get_created_at(star)}
189
+ - **Updated at**: #{get_updated_at(star)}
190
+ - **Starred at**: #{starred_at_str}
191
+
192
+ [View on GitHub](#{get_html_url(star)})
193
+
194
+ ## Topics
195
+
196
+ #{(get_topics(star) || []).map { |topic| "- #{topic}" }.join("\n")}
197
+ MARKDOWN
198
+
199
+ # Try to fetch README.md content
200
+ readme_content = fetch_readme(repo_full_name)
201
+ if readme_content
202
+ content += "\n\n## README\n\n#{readme_content}\n"
203
+ else
204
+ content += "\n\n## Description\n\n#{get_description(star)}\n"
205
+ end
206
+
207
+ File.write(filepath, content)
208
+ end
209
+
210
+ # Helper methods to safely access star properties
211
+ def get_repo_full_name(star)
212
+ if star.respond_to?(:repo) && star.repo.respond_to?(:full_name)
213
+ star.repo.full_name
214
+ elsif star.respond_to?(:full_name)
215
+ star.full_name
216
+ else
217
+ "unknown/unknown"
218
+ end
219
+ end
220
+
221
+ def get_description(star)
222
+ if star.respond_to?(:repo) && star.repo.respond_to?(:description)
223
+ star.repo.description
224
+ elsif star.respond_to?(:description)
225
+ star.description
226
+ else
227
+ ""
228
+ end
229
+ end
230
+
231
+ def get_stargazers_count(star)
232
+ if star.respond_to?(:repo) && star.repo.respond_to?(:stargazers_count)
233
+ star.repo.stargazers_count
234
+ elsif star.respond_to?(:stargazers_count)
235
+ star.stargazers_count
236
+ else
237
+ 0
238
+ end
239
+ end
240
+
241
+ def get_forks_count(star)
242
+ if star.respond_to?(:repo) && star.repo.respond_to?(:forks_count)
243
+ star.repo.forks_count
244
+ elsif star.respond_to?(:forks_count)
245
+ star.forks_count
246
+ else
247
+ 0
248
+ end
249
+ end
250
+
251
+ def get_language(star)
252
+ if star.respond_to?(:repo) && star.repo.respond_to?(:language)
253
+ star.repo.language
254
+ elsif star.respond_to?(:language)
255
+ star.language
256
+ else
257
+ "Unknown"
258
+ end
259
+ end
260
+
261
+ def get_created_at(star)
262
+ if star.respond_to?(:repo) && star.repo.respond_to?(:created_at)
263
+ star.repo.created_at
264
+ elsif star.respond_to?(:created_at)
265
+ star.created_at
266
+ else
267
+ "Unknown"
268
+ end
269
+ end
270
+
271
+ def get_updated_at(star)
272
+ if star.respond_to?(:repo) && star.repo.respond_to?(:updated_at)
273
+ star.repo.updated_at
274
+ elsif star.respond_to?(:updated_at)
275
+ star.updated_at
276
+ else
277
+ "Unknown"
278
+ end
279
+ end
280
+
281
+ def get_html_url(star)
282
+ if star.respond_to?(:repo) && star.repo.respond_to?(:html_url)
283
+ star.repo.html_url
284
+ elsif star.respond_to?(:html_url)
285
+ star.html_url
286
+ else
287
+ "https://github.com"
288
+ end
289
+ end
290
+
291
+ def get_topics(star)
292
+ if star.respond_to?(:repo) && star.repo.respond_to?(:topics)
293
+ star.repo.topics
294
+ elsif star.respond_to?(:topics)
295
+ star.topics
296
+ else
297
+ []
298
+ end
299
+ end
300
+
301
+ # Fetch README.md content from GitHub
302
+ def fetch_readme(repo_full_name)
303
+ begin
304
+ # Get README content using GitHub API
305
+ response = github.repos.contents.get(
306
+ user: repo_full_name.split('/').first,
307
+ repo: repo_full_name.split('/').last,
308
+ path: 'README.md'
309
+ )
310
+
311
+ # Decode content from Base64
312
+ if response.content && response.encoding == 'base64'
313
+ return Base64.decode64(response.content).force_encoding('UTF-8')
314
+ end
315
+ rescue Github::Error::NotFound
316
+ # Try README.markdown if README.md not found
317
+ begin
318
+ response = github.repos.contents.get(
319
+ user: repo_full_name.split('/').first,
320
+ repo: repo_full_name.split('/').last,
321
+ path: 'README.markdown'
322
+ )
323
+
324
+ if response.content && response.encoding == 'base64'
325
+ return Base64.decode64(response.content).force_encoding('UTF-8')
326
+ end
327
+ rescue Github::Error::NotFound
328
+ # Try readme.md (lowercase) if previous attempts failed
329
+ begin
330
+ response = github.repos.contents.get(
331
+ user: repo_full_name.split('/').first,
332
+ repo: repo_full_name.split('/').last,
333
+ path: 'readme.md'
334
+ )
335
+
336
+ if response.content && response.encoding == 'base64'
337
+ return Base64.decode64(response.content).force_encoding('UTF-8')
338
+ end
339
+ rescue Github::Error::NotFound
340
+ # README not found
341
+ return nil
342
+ rescue => e
343
+ puts "Error fetching lowercase readme.md for #{repo_full_name}: #{e.message}"
344
+ return nil
345
+ end
346
+ rescue => e
347
+ puts "Error fetching README.markdown for #{repo_full_name}: #{e.message}"
348
+ return nil
349
+ end
350
+ rescue => e
351
+ puts "Error fetching README.md for #{repo_full_name}: #{e.message}"
352
+ return nil
353
+ end
354
+
355
+ nil
356
+ end
357
+ end
358
+ end
359
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Star
4
+ module Dlp
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
data/lib/star/dlp.rb ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "dlp/version"
4
+ require_relative "dlp/config"
5
+ require_relative "dlp/downloader"
6
+ require_relative "dlp/cli"
7
+
8
+ module Star
9
+ module Dlp
10
+ class Error < StandardError; end
11
+
12
+ def self.start(args = ARGV)
13
+ CLI.start(args)
14
+ end
15
+ end
16
+ end
data/sig/star/dlp.rbs ADDED
@@ -0,0 +1,6 @@
1
+ module Star
2
+ module Dlp
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: star-dlp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Liu Xiang
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-03-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: github_api
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.19.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.19.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: thor
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: fileutils
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.6'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: json
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.6'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.6'
69
+ description: star-dlp is a tool that helps you download, organize, and manage repositories
70
+ you've starred on GitHub
71
+ email:
72
+ - liuxiang921@gmail.com
73
+ executables:
74
+ - star-dlp
75
+ extensions: []
76
+ extra_rdoc_files: []
77
+ files:
78
+ - ".rspec"
79
+ - ".rubocop.yml"
80
+ - ".rubocop_todo.yml"
81
+ - ".vscode/launch.json"
82
+ - CODE_OF_CONDUCT.md
83
+ - Gemfile
84
+ - Gemfile.lock
85
+ - LICENSE.txt
86
+ - README.md
87
+ - README_zh.md
88
+ - Rakefile
89
+ - exe/star-dlp
90
+ - lib/star/dlp.rb
91
+ - lib/star/dlp/cli.rb
92
+ - lib/star/dlp/config.rb
93
+ - lib/star/dlp/downloader.rb
94
+ - lib/star/dlp/version.rb
95
+ - sig/star/dlp.rbs
96
+ homepage: https://github.com/lululau/star-dlp
97
+ licenses:
98
+ - MIT
99
+ metadata:
100
+ allowed_push_host: https://rubygems.org
101
+ homepage_uri: https://github.com/lululau/star-dlp
102
+ source_code_uri: https://github.com/lululau/star-dlp
103
+ changelog_uri: https://github.com/lululau/star-dlp/blob/main/CHANGELOG.md
104
+ post_install_message:
105
+ rdoc_options: []
106
+ require_paths:
107
+ - lib
108
+ required_ruby_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: 2.6.0
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ requirements: []
119
+ rubygems_version: 3.4.1
120
+ signing_key:
121
+ specification_version: 4
122
+ summary: A Ruby gem for downloading and managing your GitHub stars
123
+ test_files: []