nonebot-plugin-osubot 6.20.0__py3-none-any.whl → 6.21.0__py3-none-any.whl

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.

Potentially problematic release.


This version of nonebot-plugin-osubot might be problematic. Click here for more details.

@@ -170,6 +170,7 @@ async def get_user_scores(
170
170
  return all_scores[:limit]
171
171
 
172
172
  elif source == "ppysb":
173
+ limit = min(limit, 100)
173
174
  url = f"https://api.ppy.sb/v1/get_player_scores?scope={scope}&id={uid}&mode={FGM[mode]}&limit={limit}&include_failed={int(include_failed)}"
174
175
  data = await make_request(url, {}, "未找到该玩家BP")
175
176
  data = ScoresResponse(**data)
@@ -6,8 +6,24 @@ function PalpableCatchHitObject(data, beatmap) {
6
6
  this.color = data.color;
7
7
  this.radius = data.radius;
8
8
  this.hyperDash = false;
9
+ this.edge = false;
9
10
  this.XDistToNext = [1, 1, 1];
11
+ this.hitSound = data.hitSound || "0";
10
12
  }
13
+
14
+ PalpableCatchHitObject.prototype.getHitsoundLabel = function (){
15
+ let hsLabels = [];
16
+ if (this.hitSound == null || this.hitSound == undefined || !this.hitSound) // hs = null | undifined | "" | "0"
17
+ return null;
18
+ else {
19
+ if (this.hitSound & 2) hsLabels.push("W");
20
+ if (this.hitSound & 8) hsLabels.push("C");
21
+ if (this.hitSound & 4) hsLabels.push("F");
22
+ if (hsLabels.length === 0) return null;
23
+ return hsLabels.join("|");
24
+ }
25
+ };
26
+
11
27
  PalpableCatchHitObject.prototype.draw = function (time, ctx) {
12
28
  var dt = this.time - time;
13
29
  if (dt >= -this.beatmap.FALLOUT_TIME) {
@@ -24,7 +40,10 @@ PalpableCatchHitObject.prototype.draw2 = function (obj, SCALE, ctx, BORDER_WIDTH
24
40
  if (this.hyperDash) this.drawDashCircle2({ x: obj.x + BORDER_WIDTH, y: obj.y + BORDER_HEIGHT }, SCALE, ctx);
25
41
  this.drawCircle2({ x: obj.x + BORDER_WIDTH, y: obj.y + BORDER_HEIGHT }, SCALE, ctx);
26
42
  }
27
- if (combo) this.drawCombo2({ x: obj.x + BORDER_WIDTH, y: obj.y + BORDER_HEIGHT }, SCALE, combo, ctx);
43
+ if (combo) {
44
+ if (typeof(combo) === "number") this.drawCombo2({ x: obj.x + BORDER_WIDTH, y: obj.y + BORDER_HEIGHT }, SCALE, "x" + combo, ctx);
45
+ else this.drawCombo2({ x: obj.x + BORDER_WIDTH, y: obj.y + BORDER_HEIGHT }, SCALE, combo, ctx);
46
+ }
28
47
  }
29
48
  PalpableCatchHitObject.prototype.predraw2 = function (SCREENSHEIGHT, COLMARGIN, SCALE, offset) {
30
49
  // 去除offset
@@ -83,7 +102,7 @@ PalpableCatchHitObject.prototype.drawCombo2 = function (position, SCALE, combo,
83
102
  ctx.font = "normal 16px 'Segoe UI'";
84
103
  ctx.textBaseline = "middle";
85
104
  ctx.textAlign = "start";
86
- ctx.fillText("x" + combo, position.x + this.radius * SCALE * 2, position.y);
105
+ ctx.fillText(combo, position.x + this.radius * SCALE * 2, position.y);
87
106
  ctx.restore();
88
107
  };
89
108
  PalpableCatchHitObject.prototype.drawBanana = function (position, ctx) {
@@ -29,5 +29,6 @@ BananaShower.prototype.buildNested = function() {
29
29
  time += spacing;
30
30
  i++;
31
31
  }
32
+ this.nested[this.nested.length - 1].hitSound = this.hitSound;
32
33
  return this;
33
34
  };
@@ -150,6 +150,7 @@ function Catch(osu, mods) {
150
150
  x: hitObject.position.x,
151
151
  color: hitObject.color,
152
152
  radius: this.circleRadius,
153
+ hitSound: hitObject.hitSound,
153
154
  }, this);
154
155
 
155
156
  this.palpableObjects.push(pch);
@@ -289,6 +290,8 @@ function Catch(osu, mods) {
289
290
  else {
290
291
  lastExcess = Math.clamp(distanceToHyper, 0, this.halfCatcherWidth);
291
292
  //this.whiteDashes.push({ score: distanceToHyper, time: currentObject.time });
293
+ // 标注再加20像素就变红果的白果跳
294
+ if (distanceToNext > 2 * this.halfCatcherWidth && distanceToHyper < 20) currentObject.edge = true;
292
295
  }
293
296
 
294
297
  lastDirection = thisDirection;
@@ -357,7 +360,7 @@ Catch.prototype.draw = function (time, ctx) {
357
360
  /**
358
361
  * @param {number} SCALE 缩放大小(0.2=缩放为1/5)
359
362
  * @param {number} SPEED 播放速度 DT=1.5 HT=0.75 在ctb不影响谱面,只影响时间和BPM标注
360
- * @param {{showDistance: boolean, distanceStart: number, distanceEnd: number, distanceType: number}} params 其他参数
363
+ * @param {{showLabelType: number, distanceStart: number, distanceEnd: number, distanceType: number}} params 其他参数
361
364
  */
362
365
  Catch.prototype.draw2 = function (SCALE, SPEED = 1, params = {}) {
363
366
  // 初定每一列20个屏幕大小,不够换列
@@ -405,7 +408,7 @@ Catch.prototype.draw2 = function (SCALE, SPEED = 1, params = {}) {
405
408
  if (this.TimingPoints[i].kiai) lastKiaiStart = this.TimingPoints[i].time - offset;
406
409
  }
407
410
  // 绿线在ctb无关紧要,正常模式不用加,标注距离时只加变化的
408
- if (params.showDistance) {
411
+ if (params.showLabelType === 1) {
409
412
  if (this.TimingPoints[i].parent) {
410
413
  if (Math.abs(this.TimingPoints[i].sliderVelocity - lastSV) < 0.001) {
411
414
  lastSV = this.TimingPoints[i].sliderVelocity;
@@ -670,25 +673,69 @@ Catch.prototype.draw2 = function (SCALE, SPEED = 1, params = {}) {
670
673
  // 按总物件数/时间控制密度
671
674
  let totalTime = this.fullCatchObjects[this.fullCatchObjects.length - 1].time - this.fullCatchObjects[0].time;
672
675
  if (this.fullCatchObjects.length * 1000 / totalTime > 2) comboSplit = Math.ceil(this.fullCatchObjects.length * 1000 / totalTime) * 10;
673
- // 按0.5*位数修约
674
- let roundBy = Math.pow(10, comboSplit.toString().length) * 0.5;
676
+ // 按0.5*(10^(位数-1&&最小为2))修约 60=>50 270=>250 820=>800 1434=>1500 1834=>2000
677
+ let roundBy = Math.pow(10, Math.max(comboSplit.toString().length - 1, 2)) * 0.5;
675
678
  comboSplit = Math.round(comboSplit / roundBy) * roundBy;
679
+ if (comboSplit <= 0) comboSplit = 20;
676
680
  for (let i = 0; i < this.fullCatchObjects.length; i++) {
677
681
  let showCombo = null;
678
682
  if (objs[i].type === "Fruit" || objs[i].type === "Droplet") {
679
683
  combo += 1;
680
684
  }
681
685
  // 借用combo位显示距离,省事!
682
- if (params.showDistance) {
686
+ if (params.showLabelType === 1) {
683
687
  if (objs[i].type === "Fruit" || objs[i].type === "Droplet") {
684
688
  let distanceType = params.distanceType;
685
689
  if (this.fullCatchObjects[i].XDistToNext[distanceType] >= params.distanceStart && this.fullCatchObjects[i].XDistToNext[distanceType] <= params.distanceEnd)
686
690
  showCombo = this.fullCatchObjects[i].XDistToNext[distanceType];
687
691
  }
688
692
  }
693
+ // 借用combo位显示音效,省事!
694
+ else if (params.showLabelType === 2) {
695
+ if (objs[i].type === "Fruit" || objs[i].type === "Banana") {
696
+ showCombo = this.fullCatchObjects[i].getHitsoundLabel();
697
+ }
698
+ }
689
699
 
690
- else if (combo > lastCombo && combo > 0 && combo % comboSplit === 0) showCombo = combo;
700
+ else if (combo > lastCombo && combo > 0 && (combo % comboSplit === 0 || this.fullCatchObjects[i].edge)) showCombo = combo;
691
701
  this.fullCatchObjects[i].draw2(objs[i], SCALE, ctx2, BORDER_WIDTH, BORDER_HEIGHT, showCombo);
702
+
703
+ // 标注edge
704
+ if (this.fullCatchObjects[i].edge && i < this.fullCatchObjects.length - 1) {
705
+ ctx2.save();
706
+ ctx2.beginPath();
707
+ let _x1 = objs[i].x + BORDER_WIDTH;
708
+ let _y1 = objs[i].y + BORDER_HEIGHT;
709
+ let _x2 = objs[i+1].x + BORDER_WIDTH;
710
+ let _y2 = objs[i+1].y + BORDER_HEIGHT;
711
+ if (_y1 > _y2) {
712
+ ctx2.moveTo(_x1, _y1);
713
+ ctx2.lineTo(_x2, _y2);
714
+ }
715
+ // 不在同一轨道上
716
+ else if (_y1 < _y2) {
717
+ let line_end_x2 = _x2 - Beatmap.WIDTH * SCALE - 2 * COLMARGIN;
718
+ let line_end_y2 = _y2 + 2 * BORDER_HEIGHT - height;
719
+ let line_middle_y2 = BORDER_HEIGHT;
720
+ let line_middle_x2 = (line_end_x2 - _x1) * (line_end_y2 - line_middle_y2) / (_y1 - line_end_y2) + line_end_x2;
721
+ if (line_middle_x2) {
722
+ ctx2.moveTo(_x1, _y1);
723
+ ctx2.lineTo(line_middle_x2, line_middle_y2);
724
+ }
725
+ let line_start_x1 = _x1 + Beatmap.WIDTH * SCALE + 2 * COLMARGIN;
726
+ let line_start_y1 = _y1 + height - 2 * BORDER_HEIGHT;
727
+ let line_middle_y1 = height - BORDER_HEIGHT;
728
+ let line_middle_x1 = (_x2 - line_start_x1) * (_y2 - line_middle_y1) / (line_start_y1 - _y2) + _x2;
729
+ if (line_middle_x1) {
730
+ ctx2.moveTo(line_middle_x1, line_middle_y1);
731
+ ctx2.lineTo(_x2, _y2);
732
+ }
733
+ }
734
+ ctx2.strokeStyle = '#fff';
735
+ ctx2.lineWidth = 3;
736
+ ctx2.stroke();
737
+ }
738
+
692
739
  lastCombo = combo;
693
740
  }
694
741
 
@@ -698,18 +745,23 @@ Catch.prototype.draw2 = function (SCALE, SPEED = 1, params = {}) {
698
745
  extraBarTimes.map((_barTime) => {
699
746
  let _objs = objs.filter((obj) => Math.abs(obj.time - _barTime * 1000) < EDGE_OFFSET);
700
747
  _objs.map((_obj) => {
701
- let distanceType = params.distanceType;
702
- let _dist = this.fullCatchObjects[_obj.index].XDistToNext[distanceType];
703
- if (_dist < params.distanceStart || _dist > params.distanceEnd) _dist = null;
748
+ let label = null;
749
+ if (params.showLabelType === 1) {
750
+ let distanceType = params.distanceType;
751
+ label = this.fullCatchObjects[_obj.index].XDistToNext[distanceType];
752
+ if (label < params.distanceStart || label > params.distanceEnd) label = null;
753
+ }
754
+ else if (params.showLabelType === 2) {
755
+ label = this.fullCatchObjects[_obj.index].getHitsoundLabel();
756
+ }
757
+
704
758
  if (_obj.y > SCREENSHEIGHT * SCALE - 5) {
705
759
  // note靠近下边缘,在上一列的上边缘再画一个
706
- if (params.showDistance) this.fullCatchObjects[_obj.index].draw2({ time: _obj.time, type: _obj.type, x: _obj.x - (Beatmap.WIDTH * SCALE + 2 * COLMARGIN), y: 0, col: _obj.col - 1 }, SCALE, ctx2, BORDER_WIDTH, BORDER_HEIGHT, _dist);
707
- else this.fullCatchObjects[_obj.index].draw2({ time: _obj.time, type: _obj.type, x: _obj.x - (Beatmap.WIDTH * SCALE + 2 * COLMARGIN), y: 0, col: _obj.col - 1 }, SCALE, ctx2, BORDER_WIDTH, BORDER_HEIGHT);
760
+ this.fullCatchObjects[_obj.index].draw2({ time: _obj.time, type: _obj.type, x: _obj.x - (Beatmap.WIDTH * SCALE + 2 * COLMARGIN), y: 0, col: _obj.col - 1 }, SCALE, ctx2, BORDER_WIDTH, BORDER_HEIGHT, label);
708
761
  }
709
762
  else if (_obj.y < 5) {
710
763
  // note靠近上边缘,在下一列的下边缘再画一个
711
- if (params.showDistance) this.fullCatchObjects[_obj.index].draw2({ time: _obj.time, type: _obj.type, x: _obj.x + (Beatmap.WIDTH * SCALE + 2 * COLMARGIN), y: SCREENSHEIGHT * SCALE, col: _obj.col + 1 }, SCALE, ctx2, BORDER_WIDTH, BORDER_HEIGHT, _dist);
712
- else this.fullCatchObjects[_obj.index].draw2({ time: _obj.time, type: _obj.type, x: _obj.x + (Beatmap.WIDTH * SCALE + 2 * COLMARGIN), y: SCREENSHEIGHT * SCALE, col: _obj.col + 1 }, SCALE, ctx2, BORDER_WIDTH, BORDER_HEIGHT);
764
+ this.fullCatchObjects[_obj.index].draw2({ time: _obj.time, type: _obj.type, x: _obj.x + (Beatmap.WIDTH * SCALE + 2 * COLMARGIN), y: SCREENSHEIGHT * SCALE, col: _obj.col + 1 }, SCALE, ctx2, BORDER_WIDTH, BORDER_HEIGHT, label);
713
765
  }
714
766
  });
715
767
  });
@@ -748,4 +800,4 @@ Catch.prototype.processProgressBar = function (ctx, totalTime) {
748
800
  ctx.fillRect(ctxwidth * time / totalTime, 0, ctxwidth * (endtime - time) / totalTime, ctxheight);
749
801
  }
750
802
  }
751
- };
803
+ };
@@ -6,12 +6,14 @@ function JuiceStream(data, beatmap)
6
6
  this.sliderType = this.points[0];
7
7
  this.points[0] = this.position;
8
8
  for (var i = 1; i < this.points.length; i++)
9
- {
9
+ {
10
10
  this.points[i] = new Point(this.points[i].split(':'));
11
11
  }
12
12
  this.repeat = data[6] | 0;
13
13
  this.pixelLength = +data[7];
14
14
 
15
+ this.hitSounds = this.hitSoundString.split('|');
16
+
15
17
  this.timingPoint = this.beatmap.timingPointAt(this.time);
16
18
  this.beatLength = this.timingPoint.beatLength;
17
19
  this.timingPointStart = this.timingPoint.time;
@@ -99,6 +101,7 @@ JuiceStream.prototype.generateTicks = function(spanIndex, spanStartTime, spanDur
99
101
  JuiceStream.prototype.buildNested = function() {
100
102
  this.nested = [];
101
103
 
104
+ let nestedIndex = 0;
102
105
  let lastEvent = null;
103
106
  for(let i = 0; i < this.events.length; i++) {
104
107
  // generate tiny droplets since the last point
@@ -151,7 +154,9 @@ JuiceStream.prototype.buildNested = function() {
151
154
  x: this.curve.pointAt(this.events[i].pathProgress).x,
152
155
  color: this.color,
153
156
  radius: this.beatmap.circleRadius,
157
+ hitSound: this.hitSounds[nestedIndex] || "0",
154
158
  }, this.beatmap));
159
+ nestedIndex++;
155
160
  break;
156
161
  }
157
162
  }
@@ -21,5 +21,25 @@ Curve.parse = function(sliderType, points, pixelLength)
21
21
  }
22
22
  }
23
23
  catch(e) {}
24
- return new LinearBezier(points, pixelLength, sliderType == 'L');
24
+ try
25
+ {
26
+ return new LinearBezier(points, pixelLength, sliderType == 'L');
27
+ }
28
+ catch(e)
29
+ {
30
+ return new SingleNoteCurve(points[0]);
31
+ }
25
32
  }
33
+
34
+
35
+
36
+ function SingleNoteCurve(point)
37
+ {
38
+ this.path = [point];
39
+ };
40
+ SingleNoteCurve.prototype = Object.create(Curve.prototype);
41
+ SingleNoteCurve.prototype.constructor = SingleNoteCurve;
42
+ SingleNoteCurve.prototype.pointAt = function(t)
43
+ {
44
+ return this.path[0];
45
+ };
@@ -12,6 +12,8 @@ function Slider(data, beatmap)
12
12
  this.repeat = data[6] | 0;
13
13
  this.pixelLength = +data[7];
14
14
 
15
+ this.hitSoundString = data[8] || "";
16
+
15
17
  var sliderTime = this.beatmap.timingPointAt(this.time).beatLength * (
16
18
  this.pixelLength / this.beatmap.SliderMultiplier
17
19
  ) / 100;
@@ -28,14 +28,18 @@ from .utils import (
28
28
  open_user_icon,
29
29
  filter_scores_with_regex,
30
30
  trim_text_with_ellipsis,
31
+ draw_text_with_outline,
31
32
  )
32
33
  from .static import (
33
34
  Image,
34
35
  IconLs,
35
- Venera_75,
36
+ Venera_60,
37
+ Torus_Regular_15,
36
38
  Torus_Regular_20,
37
- Torus_Regular_30,
38
- Torus_Regular_75,
39
+ Torus_Regular_25,
40
+ Torus_Regular_50,
41
+ Torus_Regular_60,
42
+ Torus_SemiBold_15,
39
43
  Torus_SemiBold_20,
40
44
  Torus_SemiBold_25,
41
45
  Torus_SemiBold_30,
@@ -197,15 +201,23 @@ async def draw_score_pic(score_info: UnifiedScore, info: UnifiedUser, map_json,
197
201
  original_ss_pp_info = get_ss_pp(str(osu.absolute()), 0, is_lazer)
198
202
  if_pp, ss_pp = get_if_pp_ss_pp(score_info, str(osu.absolute()), is_lazer)
199
203
  # 新建图片
200
- im = Image.new("RGBA", (1500, 720))
204
+ im = Image.new("RGBA", (1280, 720))
201
205
  draw = ImageDraw.Draw(im)
202
206
  # 获取cover并裁剪,高斯,降低亮度
203
207
  try:
204
208
  bg = await get_bg(mapinfo.id, mapinfo.beatmapset_id)
205
- cover_crop = await crop_bg((1500, 720), bg)
206
- cover_gb = cover_crop.filter(ImageFilter.GaussianBlur(3))
207
- cover_img = ImageEnhance.Brightness(cover_gb).enhance(2 / 4.0)
208
- im.alpha_composite(cover_img, (0, 0))
209
+ cover_crop_left = await crop_bg((1280, 720), bg)
210
+ cover_gb_left = cover_crop_left.filter(ImageFilter.GaussianBlur(10))
211
+ cover_gb_left = ImageEnhance.Brightness(cover_gb_left).enhance(1)
212
+ cover_img_left = ImageEnhance.Brightness(cover_gb_left).enhance(2 / 4.0)
213
+ im.alpha_composite(cover_img_left, (0, 0))
214
+ # 新小图 BG
215
+ cover_crop_right = await crop_bg((650, 270), bg)
216
+ cover_gb_right = cover_crop_right.filter(ImageFilter.GaussianBlur(1))
217
+ cover_gb_right = ImageEnhance.Brightness(cover_gb_right).enhance(0.9)
218
+ cover_img_right = ImageEnhance.Brightness(cover_gb_right).enhance(1)
219
+ cover_img_right = draw_fillet(cover_img_right, 15)
220
+ im.alpha_composite(cover_img_right, (0, 50))
209
221
  except NetworkError:
210
222
  ...
211
223
  # 获取成绩背景做底图
@@ -213,58 +225,65 @@ async def draw_score_pic(score_info: UnifiedScore, info: UnifiedUser, map_json,
213
225
  recent_bg = Image.open(bg).convert("RGBA")
214
226
  im.alpha_composite(recent_bg)
215
227
  # 模式
216
- draw.text((75, 75), IconLs[score_info.ruleset_id], font=extra_30, anchor="lt")
228
+ draw_text_with_outline(
229
+ draw,
230
+ (30, 80),
231
+ IconLs[score_info.ruleset_id],
232
+ extra_30,
233
+ anchor="lm",
234
+ fill=(255, 255, 255, 255),
235
+ )
217
236
  # 难度星星
218
237
  stars_bg = stars_diff(pp_info.difficulty.stars)
219
238
  stars_img = stars_bg.resize((85, 37))
220
- im.alpha_composite(stars_img, (122, 72))
239
+ im.alpha_composite(stars_img, (552, 67))
221
240
  if pp_info.difficulty.stars < 6.5:
222
241
  color = (0, 0, 0, 255)
223
242
  else:
224
243
  color = (255, 217, 102, 255)
225
244
  # 星级
226
245
  draw.text(
227
- (128, 90),
246
+ (556, 85),
228
247
  f"★{pp_info.difficulty.stars:.2f}",
229
248
  font=Torus_SemiBold_20,
230
249
  anchor="lm",
231
250
  fill=color,
232
251
  )
233
252
  # mods
234
- if any(i in score_info.mods for i in (Mod(acronym="HD"), Mod(acronym="FL"), Mod(acronym="FI"))):
235
- ranking = ["XH", "SH", "A", "B", "C", "D", "F"]
236
- else:
237
- ranking = ["X", "S", "A", "B", "C", "D", "F"]
253
+ # if any(i in score_info.mods for i in (Mod(acronym="HD"), Mod(acronym="FL"), Mod(acronym="FI"))):
254
+ # ranking = ["XH", "SH", "A", "B", "C", "D", "F"]
255
+ # else:
256
+ # ranking = ["X", "S", "A", "B", "C", "D", "F"]
238
257
  if score_info.mods:
239
258
  for mods_num, s_mods in enumerate(score_info.mods):
240
259
  mods_bg = osufile / "mods" / f"{s_mods.acronym}.png"
241
260
  try:
242
261
  mods_img = Image.open(mods_bg).convert("RGBA")
243
- im.alpha_composite(mods_img, (500 + 50 * mods_num, 160))
262
+ im.alpha_composite(mods_img, (880 + 50 * mods_num, 100))
244
263
  except FileNotFoundError:
245
264
  pass
246
265
  # 成绩S-F
247
- rank_ok = False
248
- for rank_num, i in enumerate(ranking):
249
- rank_img = osufile / "ranking" / f"ranking-{i}.png"
250
- if rank_ok:
251
- rank_b = Image.open(rank_img).convert("RGBA").resize((48, 24))
252
- rank_new = Image.new("RGBA", rank_b.size, (0, 0, 0, 0))
253
- rank_bg = Image.blend(rank_new, rank_b, 0.5)
254
- elif i != score_info.rank:
255
- rank_b = Image.open(rank_img).convert("RGBA").resize((48, 24))
256
- rank_new = Image.new("RGBA", rank_b.size, (0, 0, 0, 0))
257
- rank_bg = Image.blend(rank_new, rank_b, 0.2)
258
- else:
259
- rank_bg = Image.open(rank_img).convert("RGBA").resize((48, 24))
260
- rank_ok = True
261
- im.alpha_composite(rank_bg, (75, 163 + 39 * rank_num))
266
+ # rank_ok = False
267
+ # for rank_num, i in enumerate(ranking):
268
+ # rank_img = osufile / "ranking" / f"ranking-{i}.png"
269
+ # if rank_ok:
270
+ # rank_b = Image.open(rank_img).convert("RGBA").resize((48, 24))
271
+ # rank_new = Image.new("RGBA", rank_b.size, (0, 0, 0, 0))
272
+ # rank_bg = Image.blend(rank_new, rank_b, 0.5)
273
+ # elif i != score_info.rank:
274
+ # rank_b = Image.open(rank_img).convert("RGBA").resize((48, 24))
275
+ # rank_new = Image.new("RGBA", rank_b.size, (0, 0, 0, 0))
276
+ # rank_bg = Image.blend(rank_new, rank_b, 0.2)
277
+ # else:
278
+ # rank_bg = Image.open(rank_img).convert("RGBA").resize((48, 24))
279
+ # rank_ok = True
280
+ # im.alpha_composite(rank_bg, (75, 163 + 39 * rank_num))
262
281
  # 成绩+acc
263
282
  im = draw_acc(im, score_info.accuracy, score_info.ruleset_id)
264
283
  # 地区
265
284
  country = osufile / "flags" / f"{info.country_code}.png"
266
285
  country_bg = Image.open(country).convert("RGBA").resize((66, 45))
267
- im.alpha_composite(country_bg, (250, 577))
286
+ im.alpha_composite(country_bg, (208, 597))
268
287
  if info.team and info.team.flag_url:
269
288
  team_path = team_cache_path / f"{info.team.id}.png"
270
289
  if not team_path.exists():
@@ -273,11 +292,11 @@ async def draw_score_pic(score_info: UnifiedScore, info: UnifiedUser, map_json,
273
292
  team_img.save(team_path)
274
293
  try:
275
294
  team_img = Image.open(team_path).convert("RGBA").resize((80, 40))
276
- im.alpha_composite(team_img, (250, 640))
295
+ im.alpha_composite(team_img, (208, 660))
277
296
  except UnidentifiedImageError:
278
297
  team_path.unlink()
279
298
  raise NetworkError("team 图片下载错误,请重试!")
280
- draw.text((340, 660), info.team.name, font=Torus_Regular_20, anchor="lt")
299
+ draw.text((297, 675), info.team.name, font=Torus_Regular_20, anchor="lt")
281
300
  # supporter
282
301
  # if info.is_supporter:
283
302
  # im.alpha_composite(SupporterBg.resize((40, 40)), (250, 640))
@@ -309,38 +328,38 @@ async def draw_score_pic(score_info: UnifiedScore, info: UnifiedUser, map_json,
309
328
  ]
310
329
 
311
330
  for num, (orig, new) in enumerate(zip(original_mapdiff, mapdiff)):
312
- orig_difflen = int(250 * max(0, orig) / 10) if orig <= 10 else 250
313
- new_difflen = int(250 * max(0, new) / 10) if new <= 10 else 250
331
+ orig_difflen = int(400 * max(0, orig) / 10) if orig <= 10 else 400
332
+ new_difflen = int(400 * max(0, new) / 10) if new <= 10 else 400
314
333
  if is_close(new, orig):
315
334
  color = (255, 255, 255, 255)
316
335
  diff_len = Image.new("RGBA", (orig_difflen, 8), color)
317
- im.alpha_composite(diff_len, (1190, 306 + 35 * num))
336
+ im.alpha_composite(diff_len, (165, 352 + 35 * num))
318
337
  elif new > orig and not (score_info.ruleset_id == 3 and mapinfo.mode == "osu"):
319
338
  color = (198, 92, 102, 255)
320
339
  orig_color = (246, 136, 144, 255)
321
340
  new_diff_len = Image.new("RGBA", (new_difflen, 8), color)
322
- im.alpha_composite(new_diff_len, (1190, 306 + 35 * num))
341
+ im.alpha_composite(new_diff_len, (165, 352 + 35 * num))
323
342
  orig_diff_len = Image.new("RGBA", (orig_difflen, 8), orig_color)
324
- im.alpha_composite(orig_diff_len, (1190, 306 + 35 * num))
343
+ im.alpha_composite(orig_diff_len, (165, 352 + 35 * num))
325
344
  elif new < orig and not (score_info.ruleset_id == 3 and mapinfo.mode == "osu"):
326
345
  color = (161, 212, 238, 255)
327
346
  orig_color = (255, 255, 255, 255)
328
347
  orig_diff_len = Image.new("RGBA", (orig_difflen, 8), orig_color)
329
- im.alpha_composite(orig_diff_len, (1190, 306 + 35 * num))
348
+ im.alpha_composite(orig_diff_len, (165, 352 + 35 * num))
330
349
  new_diff_len = Image.new("RGBA", (new_difflen, 8), color)
331
- im.alpha_composite(new_diff_len, (1190, 306 + 35 * num))
350
+ im.alpha_composite(new_diff_len, (165, 352 + 35 * num))
332
351
  else:
333
352
  raise Exception("没有这种情况")
334
353
  if new == round(new):
335
354
  draw.text(
336
- (1470, 310 + 35 * num),
355
+ (610, 355 + 35 * num),
337
356
  f"{new:.0f}",
338
357
  font=Torus_SemiBold_20,
339
358
  anchor="mm",
340
359
  )
341
360
  else:
342
361
  draw.text(
343
- (1470, 310 + 35 * num),
362
+ (610, 355 + 35 * num),
344
363
  f"{new:.2f}" if new != round(new, 1) else f"{new:.1f}",
345
364
  font=Torus_SemiBold_20,
346
365
  anchor="mm",
@@ -351,253 +370,291 @@ async def draw_score_pic(score_info: UnifiedScore, info: UnifiedUser, map_json,
351
370
  if stars > original_stars:
352
371
  color = (198, 92, 102, 255)
353
372
  orig_color = (246, 111, 34, 255)
354
- new_difflen = int(250 * max(0.0, stars) / 10) if stars <= 10 else 250
373
+ new_difflen = int(400 * max(0.0, stars) / 10) if stars <= 10 else 400
355
374
  new_diff_len = Image.new("RGBA", (new_difflen, 8), color)
356
- im.alpha_composite(new_diff_len, (1190, 446))
357
- orig_difflen = int(250 * max(0.0, original_stars) / 10) if original_stars <= 10 else 250
375
+ im.alpha_composite(new_diff_len, (165, 490))
376
+ orig_difflen = int(400 * max(0.0, original_stars) / 10) if original_stars <= 10 else 400
358
377
  orig_diff_len = Image.new("RGBA", (orig_difflen, 8), orig_color)
359
- im.alpha_composite(orig_diff_len, (1190, 446))
378
+ im.alpha_composite(orig_diff_len, (165, 490))
360
379
  elif stars < original_stars:
361
380
  color = (161, 187, 127, 255)
362
381
  orig_color = (255, 204, 34, 255)
363
- orig_difflen = int(250 * max(0.0, original_stars) / 10) if original_stars <= 10 else 250
382
+ orig_difflen = int(400 * max(0.0, original_stars) / 10) if original_stars <= 10 else 400
364
383
  orig_diff_len = Image.new("RGBA", (orig_difflen, 8), orig_color)
365
- im.alpha_composite(orig_diff_len, (1190, 446))
366
- new_difflen = int(250 * max(0.0, stars) / 10) if stars <= 10 else 250
384
+ im.alpha_composite(orig_diff_len, (165, 490))
385
+ new_difflen = int(400 * max(0.0, stars) / 10) if stars <= 10 else 400
367
386
  new_diff_len = Image.new("RGBA", (new_difflen, 8), color)
368
- im.alpha_composite(new_diff_len, (1190, 446))
387
+ im.alpha_composite(new_diff_len, (165, 490))
369
388
  else:
370
389
  color = (255, 204, 34, 255)
371
- difflen = int(250 * stars / 10) if stars <= 10 else 250
390
+ difflen = int(400 * stars / 10) if stars <= 10 else 400
372
391
  diff_len = Image.new("RGBA", (difflen, 8), color)
373
- im.alpha_composite(diff_len, (1190, 446))
374
- draw.text((1470, 450), f"{stars:.2f}", font=Torus_SemiBold_20, anchor="mm")
392
+ im.alpha_composite(diff_len, (165, 490))
393
+ draw.text((610, 493), f"{stars:.2f}", font=Torus_SemiBold_20, anchor="mm")
375
394
  # 时长 - 滑条
376
- diff_info = (
395
+ diff_info = [
377
396
  calc_songlen(mapinfo.total_length),
378
397
  f"{mapinfo.bpm:.1f}",
379
398
  mapinfo.count_circles,
380
399
  mapinfo.count_sliders,
381
- )
400
+ ] + ([mapinfo.count_spinners] if score_info.ruleset_id != 3 else [])
382
401
  for num, i in enumerate(diff_info):
383
- draw.text(
384
- (1070 + 120 * num, 245),
402
+ draw_text_with_outline(
403
+ draw,
404
+ (70 + 120 * num, 300),
385
405
  f"{i}",
386
406
  font=Torus_Regular_20,
387
407
  anchor="lm",
388
408
  fill=(255, 204, 34, 255),
389
409
  )
390
410
  # 状态
391
- draw.text((1400, 184), mapinfo.status.capitalize(), font=Torus_SemiBold_20, anchor="mm")
411
+ draw_text_with_outline(
412
+ draw,
413
+ (595, 115),
414
+ mapinfo.status.capitalize(),
415
+ Torus_SemiBold_15,
416
+ anchor="mm",
417
+ fill=(255, 255, 255, 255),
418
+ )
419
+ # setid
420
+ draw.text((20, 25), f"Setid:{mapinfo.beatmapset_id}", font=Torus_SemiBold_20, anchor="lm")
392
421
  # mapid
393
- draw.text((1485, 90), f"Mapid: {mapinfo.id}", font=Torus_SemiBold_20, anchor="rm")
422
+ draw.text((650, 25), f"Mapid: {mapinfo.id}", font=Torus_SemiBold_20, anchor="rm")
394
423
  # 曲名
395
- draw.text(
396
- (75, 38),
397
- f"{mapinfo.beatmapset.title} | by {mapinfo.beatmapset.artist_unicode}",
398
- font=Torus_SemiBold_30,
424
+ title = trim_text_with_ellipsis(mapinfo.beatmapset.title, 600, Torus_SemiBold_30)
425
+ draw_text_with_outline(
426
+ draw,
427
+ (30, 200),
428
+ title,
429
+ Torus_SemiBold_30,
399
430
  anchor="lm",
431
+ fill=(255, 255, 255, 255),
400
432
  )
401
- # 谱面版本,mapper
433
+ # 艺术家
434
+ artist = trim_text_with_ellipsis(mapinfo.beatmapset.artist, 600, Torus_SemiBold_20)
435
+ draw_text_with_outline(
436
+ draw,
437
+ (30, 230),
438
+ artist,
439
+ Torus_SemiBold_20,
440
+ anchor="lm",
441
+ fill=(255, 255, 255, 255),
442
+ )
443
+ # mapper
402
444
  if mapinfo.owners:
403
445
  owner_names = [owner.username for owner in mapinfo.owners]
404
446
  owners_str = ", ".join(owner_names)
405
- mapper = f"{mapinfo.version} | 谱师: {owners_str}"
447
+ mapper = f"谱师: {owners_str}"
406
448
 
407
449
  else:
408
- mapper = f"{mapinfo.version} | 谱师: {mapinfo.beatmapset.creator}"
409
- mapper = trim_text_with_ellipsis(mapper, 1000, Torus_SemiBold_20)
410
-
411
- draw.text(
412
- (225, 90),
450
+ mapper = f"谱师: {mapinfo.beatmapset.creator}"
451
+ mapper = trim_text_with_ellipsis(mapper, 600, Torus_SemiBold_15)
452
+ draw_text_with_outline(
453
+ draw,
454
+ (30, 265),
413
455
  mapper,
414
- font=Torus_SemiBold_20,
456
+ Torus_SemiBold_15,
457
+ anchor="lm",
458
+ fill=(255, 255, 255, 255),
459
+ )
460
+ # 谱面版本
461
+ version = trim_text_with_ellipsis(mapinfo.version, 450, Torus_SemiBold_15)
462
+ draw_text_with_outline(
463
+ draw,
464
+ (65, 80),
465
+ version,
466
+ Torus_SemiBold_15,
415
467
  anchor="lm",
468
+ fill=(255, 255, 255, 255),
416
469
  )
417
470
  # 评价
418
- draw.text((316, 307), score_info.rank, font=Venera_75, anchor="mm")
471
+ draw.text((772, 185), score_info.rank, font=Venera_60, anchor="mm")
419
472
  # 分数
420
473
  draw.text(
421
- (498, 251),
474
+ (880, 165),
422
475
  f"{score_info.legacy_total_score or score_info.total_score:,}",
423
- font=Torus_Regular_75,
476
+ font=Torus_Regular_60,
424
477
  anchor="lm",
425
478
  )
426
479
  # 时间
427
- draw.text((498, 341), "达成时间:", font=Torus_SemiBold_20, anchor="lm")
428
- draw.text((630, 341), score_info.ended_at.strftime("%Y-%m-%d %H:%M:%S"), font=Torus_SemiBold_20, anchor="lm")
480
+ draw.text((883, 260), "达成时间:", font=Torus_SemiBold_20, anchor="lm")
481
+ draw.text((985, 260), score_info.ended_at.strftime("%Y-%m-%d %H:%M:%S"), font=Torus_SemiBold_20, anchor="lm")
429
482
  # 全球排名
430
- draw.text((583, 410), f"#{grank}" if grank else "", font=Torus_SemiBold_25, anchor="mm")
483
+ draw.text((715, 300), "全球排行:" if grank else "", font=Torus_SemiBold_20, anchor="lm")
484
+ draw.text((817, 300), f"#{grank}" if grank else "", font=Torus_SemiBold_25, anchor="lm")
431
485
  # 左下玩家名
432
- draw.text((250, 530), info.username, font=Torus_SemiBold_30, anchor="lm")
486
+ draw.text((208, 550), info.username, font=Torus_SemiBold_30, anchor="lm")
433
487
  # 国内排名
434
488
  draw.text(
435
- (325, 610),
489
+ (283, 630),
436
490
  f"#{info.statistics.country_rank}",
437
491
  font=Torus_SemiBold_25,
438
492
  anchor="lm",
439
493
  )
440
494
  if score_info.ruleset_id in {0, 4, 8}:
441
- draw.text((720, 550), ss_pp, font=Torus_Regular_30, anchor="mm")
442
- draw.text((840, 550), if_pp, font=Torus_Regular_30, anchor="mm")
443
- draw.text((960, 550), f"{pp_info.pp:.0f}", font=Torus_Regular_30, anchor="mm")
444
- draw.text((720, 645), f"{pp_info.pp_aim:.0f}", font=Torus_Regular_30, anchor="mm")
445
- draw.text((840, 645), f"{pp_info.pp_speed:.0f}", font=Torus_Regular_30, anchor="mm")
446
- draw.text((960, 645), f"{pp_info.pp_accuracy:.0f}", font=Torus_Regular_30, anchor="mm")
495
+ draw.text((1066, 393), ss_pp, font=Torus_Regular_25, anchor="mm")
496
+ draw.text((933, 393), if_pp, font=Torus_Regular_25, anchor="mm")
497
+ draw.text((768, 438), f"{pp_info.pp:.0f}", font=Torus_Regular_50, anchor="mm")
498
+ draw.text((933, 482), f"{pp_info.pp_aim:.0f}", font=Torus_Regular_25, anchor="mm")
499
+ draw.text((1066, 482), f"{pp_info.pp_speed:.0f}", font=Torus_Regular_25, anchor="mm")
500
+ draw.text((1200, 482), f"{pp_info.pp_accuracy:.0f}", font=Torus_Regular_25, anchor="mm")
447
501
  draw.text(
448
- (1157, 550),
502
+ (768, 577),
449
503
  f"{score_info.accuracy:.2f}%",
450
- font=Torus_Regular_30,
504
+ font=Torus_Regular_25,
451
505
  anchor="mm",
452
506
  )
453
507
  draw.text(
454
- (1385, 550),
508
+ (768, 666),
455
509
  f"{score_info.max_combo:,}/{pp_info.difficulty.max_combo}",
456
- font=Torus_Regular_30,
510
+ font=Torus_Regular_25,
457
511
  anchor="mm",
458
512
  )
459
513
  draw.text(
460
- (1100, 645),
514
+ (933, 577),
461
515
  f"{score_info.statistics.great or 0}",
462
- font=Torus_Regular_30,
516
+ font=Torus_Regular_25,
463
517
  anchor="mm",
464
518
  )
465
519
  draw.text(
466
- (1214, 645),
520
+ (1066, 577),
467
521
  f"{score_info.statistics.ok or 0}",
468
- font=Torus_Regular_30,
522
+ font=Torus_Regular_25,
469
523
  anchor="mm",
470
524
  )
471
525
  draw.text(
472
- (1328, 645),
526
+ (933, 666),
473
527
  f"{score_info.statistics.meh or 0}",
474
- font=Torus_Regular_30,
528
+ font=Torus_Regular_25,
475
529
  anchor="mm",
476
530
  )
477
531
  draw.text(
478
- (1442, 645),
532
+ (1066, 666),
479
533
  f"{score_info.statistics.miss or 0}",
480
- font=Torus_Regular_30,
534
+ font=Torus_Regular_25,
481
535
  anchor="mm",
482
536
  )
483
537
  elif score_info.ruleset_id in {1, 5}:
484
538
  draw.text(
485
- (1118, 550),
539
+ (768, 577),
486
540
  f"{score_info.accuracy:.2f}%",
487
- font=Torus_Regular_30,
541
+ font=Torus_Regular_25,
488
542
  anchor="mm",
489
543
  )
490
- draw.text((1270, 550), f"{score_info.max_combo:,}", font=Torus_Regular_30, anchor="mm")
491
- draw.text((1420, 550), f"{pp_info.pp:.0f}/{ss_pp}", font=Torus_Regular_30, anchor="mm")
544
+ draw.text((768, 666), f"{score_info.max_combo:,}", font=Torus_Regular_25, anchor="mm")
545
+ draw.text((768, 438), f"{pp_info.pp:.0f}", font=Torus_Regular_50, anchor="mm")
546
+ draw.text((933, 393), f"{ss_pp}", font=Torus_Regular_25, anchor="mm")
492
547
  draw.text(
493
- (1118, 645),
548
+ (933, 577),
494
549
  f"{score_info.statistics.great or 0}",
495
- font=Torus_Regular_30,
550
+ font=Torus_Regular_25,
496
551
  anchor="mm",
497
552
  )
498
553
  draw.text(
499
- (1270, 645),
554
+ (1066, 577),
500
555
  f"{score_info.statistics.ok or 0}",
501
- font=Torus_Regular_30,
556
+ font=Torus_Regular_25,
502
557
  anchor="mm",
503
558
  )
504
559
  draw.text(
505
- (1420, 645),
560
+ (933, 666),
506
561
  f"{score_info.statistics.miss or 0}",
507
- font=Torus_Regular_30,
562
+ font=Torus_Regular_25,
508
563
  anchor="mm",
509
564
  )
510
565
  elif score_info.ruleset_id in {2, 6}:
511
566
  draw.text(
512
- (1083, 550),
567
+ (768, 577),
513
568
  f"{score_info.accuracy:.2f}%",
514
- font=Torus_Regular_30,
569
+ font=Torus_Regular_25,
515
570
  anchor="mm",
516
571
  )
517
572
  draw.text(
518
- (1247, 550),
573
+ (768, 666),
519
574
  f"{score_info.max_combo}/{pp_info.difficulty.max_combo}",
520
- font=Torus_Regular_30,
575
+ font=Torus_Regular_25,
521
576
  anchor="mm",
522
577
  )
523
- draw.text((1411, 550), f"{pp_info.pp:.0f}/{ss_pp}", font=Torus_Regular_30, anchor="mm")
578
+ draw.text((768, 438), f"{pp_info.pp:.0f}", font=Torus_Regular_50, anchor="mm")
579
+ draw.text((933, 393), f"{ss_pp}", font=Torus_Regular_25, anchor="mm")
524
580
  draw.text(
525
- (1062, 645),
581
+ (933, 577),
526
582
  f"{score_info.statistics.great or 0}",
527
- font=Torus_Regular_30,
583
+ font=Torus_Regular_25,
528
584
  anchor="mm",
529
585
  )
530
586
  draw.text(
531
- (1185, 645),
587
+ (1066, 577),
532
588
  f"{score_info.statistics.large_tick_hit or 0}",
533
- font=Torus_Regular_30,
589
+ font=Torus_Regular_25,
534
590
  anchor="mm",
535
591
  )
536
592
  draw.text(
537
- (1309, 645),
593
+ (933, 666),
538
594
  f"{score_info.statistics.small_tick_miss or 0}",
539
- font=Torus_Regular_30,
595
+ font=Torus_Regular_25,
540
596
  anchor="mm",
541
597
  )
542
598
  draw.text(
543
- (1432, 645),
599
+ (1066, 666),
544
600
  f"{score_info.statistics.miss or 0}",
545
- font=Torus_Regular_30,
601
+ font=Torus_Regular_25,
546
602
  anchor="mm",
547
603
  )
548
604
  else:
549
605
  draw.text(
550
- (1002, 580),
606
+ (933, 600),
551
607
  (
552
608
  f"{score_info.statistics.perfect / score_info.statistics.great:.1f}:1"
553
609
  if (score_info.statistics.great or 0) != 0
554
610
  else "∞:1"
555
611
  ),
556
- font=Torus_Regular_20,
612
+ font=Torus_Regular_15,
557
613
  anchor="mm",
558
614
  )
559
615
  draw.text(
560
- (1002, 550),
616
+ (768, 577),
561
617
  f"{score_info.accuracy:.2f}%",
562
- font=Torus_Regular_30,
618
+ font=Torus_Regular_25,
563
619
  anchor="mm",
564
620
  )
565
- draw.text((1197, 550), f"{score_info.max_combo}", font=Torus_Regular_30, anchor="mm")
566
- draw.text((1395, 550), f"{pp_info.pp:.0f}/{ss_pp}", font=Torus_Regular_30, anchor="mm")
621
+ draw.text((768, 666), f"{score_info.max_combo}", font=Torus_Regular_25, anchor="mm")
622
+ draw.text((768, 438), f"{pp_info.pp:.0f}", font=Torus_Regular_50, anchor="mm")
623
+ draw.text((933, 393), f"{ss_pp}", font=Torus_Regular_25, anchor="mm")
567
624
  draw.text(
568
- (953, 645),
625
+ (933, 577),
569
626
  f"{score_info.statistics.perfect or 0}",
570
- font=Torus_Regular_30,
627
+ font=Torus_Regular_25,
571
628
  anchor="mm",
572
629
  )
573
630
  draw.text(
574
- (1051, 645),
631
+ (1066, 577),
575
632
  f"{score_info.statistics.great or 0}",
576
- font=Torus_Regular_30,
633
+ font=Torus_Regular_25,
577
634
  anchor="mm",
578
635
  )
579
636
  draw.text(
580
- (1150, 645),
637
+ (1200, 577),
581
638
  f"{score_info.statistics.good or 0}",
582
- font=Torus_Regular_30,
639
+ font=Torus_Regular_25,
583
640
  anchor="mm",
584
641
  )
585
642
  draw.text(
586
- (1249, 645),
643
+ (933, 666),
587
644
  f"{score_info.statistics.ok or 0}",
588
- font=Torus_Regular_30,
645
+ font=Torus_Regular_25,
589
646
  anchor="mm",
590
647
  )
591
648
  draw.text(
592
- (1347, 645),
649
+ (1066, 666),
593
650
  f"{score_info.statistics.meh or 0}",
594
- font=Torus_Regular_30,
651
+ font=Torus_Regular_25,
595
652
  anchor="mm",
596
653
  )
597
654
  draw.text(
598
- (1445, 645),
655
+ (1200, 666),
599
656
  f"{score_info.statistics.miss or 0}",
600
- font=Torus_Regular_30,
657
+ font=Torus_Regular_25,
601
658
  anchor="mm",
602
659
  )
603
660
  user_icon = await open_user_icon(info, source)
@@ -607,7 +664,7 @@ async def draw_score_pic(score_info: UnifiedScore, info: UnifiedUser, map_json,
607
664
  if not getattr(user_icon, "is_animated", False):
608
665
  icon_bg = user_icon.convert("RGBA").resize((170, 170))
609
666
  icon_img = draw_fillet(icon_bg, 15)
610
- im.alpha_composite(icon_img, (60, 510))
667
+ im.alpha_composite(icon_img, (27, 532))
611
668
  byt = BytesIO()
612
669
  im.convert("RGB").save(byt, "jpeg")
613
670
  im.close()
@@ -620,7 +677,7 @@ async def draw_score_pic(score_info: UnifiedScore, info: UnifiedUser, map_json,
620
677
  # 创建一个新的 RGBA 图片,将 PNG 图片作为背景,将当前帧添加到背景上
621
678
  rgba_frame = Image.new("RGBA", im.size, (0, 0, 0, 0))
622
679
  rgba_frame.paste(im, (0, 0), im)
623
- rgba_frame.paste(gif_frame, (60, 510), gif_frame)
680
+ rgba_frame.paste(gif_frame, (27, 532), gif_frame)
624
681
  # 将 RGBA 图片转换为 RGB 模式,并添加到 GIF 图片中
625
682
  gif_frames.append(rgba_frame)
626
683
  gif_bytes = BytesIO()
@@ -15,14 +15,15 @@ Torus_Regular_35 = ImageFont.truetype(str(osufile / "fonts" / "Torus Regular.otf
15
15
  Torus_Regular_40 = ImageFont.truetype(str(osufile / "fonts" / "Torus Regular.otf"), 40)
16
16
  Torus_Regular_45 = ImageFont.truetype(str(osufile / "fonts" / "Torus Regular.otf"), 45)
17
17
  Torus_Regular_50 = ImageFont.truetype(str(osufile / "fonts" / "Torus Regular.otf"), 50)
18
- Torus_Regular_75 = ImageFont.truetype(str(osufile / "fonts" / "Torus Regular.otf"), 75)
18
+ Torus_Regular_60 = ImageFont.truetype(str(osufile / "fonts" / "Torus Regular.otf"), 60)
19
+ Torus_SemiBold_15 = ImageFont.truetype(str(osufile / "fonts" / "Torus SemiBold.otf"), 15)
19
20
  Torus_SemiBold_20 = ImageFont.truetype(str(osufile / "fonts" / "Torus SemiBold.otf"), 20)
20
21
  Torus_SemiBold_25 = ImageFont.truetype(str(osufile / "fonts" / "Torus SemiBold.otf"), 25)
21
22
  Torus_SemiBold_30 = ImageFont.truetype(str(osufile / "fonts" / "Torus SemiBold.otf"), 30)
22
23
  Torus_SemiBold_40 = ImageFont.truetype(str(osufile / "fonts" / "Torus SemiBold.otf"), 40)
23
24
  Torus_SemiBold_45 = ImageFont.truetype(str(osufile / "fonts" / "Torus SemiBold.otf"), 45)
24
25
  Torus_SemiBold_50 = ImageFont.truetype(str(osufile / "fonts" / "Torus SemiBold.otf"), 50)
25
- Venera_75 = ImageFont.truetype(str(osufile / "fonts" / "Venera.otf"), 75)
26
+ Venera_60 = ImageFont.truetype(str(osufile / "fonts" / "Venera.otf"), 60)
26
27
  extra_30 = ImageFont.truetype(str(osufile / "fonts" / "Extra.otf"), 30)
27
28
 
28
29
  InfoImg = Image.open(osufile / "info.png").convert("RGBA")
@@ -120,8 +120,8 @@ def draw_acc(img: Image, acc: float, mode: int):
120
120
  ax.clear()
121
121
  fig.clf()
122
122
  fig.clear()
123
- score_acc_img = Image.open(acc_img).convert("RGBA").resize((576, 432))
124
- img.alpha_composite(score_acc_img, (25, 83))
123
+ score_acc_img = Image.open(acc_img).convert("RGBA").resize((384, 288))
124
+ img.alpha_composite(score_acc_img, (580, 35))
125
125
  return img
126
126
 
127
127
 
@@ -438,3 +438,18 @@ def trim_text_with_ellipsis(text, max_width, font):
438
438
 
439
439
  # 返回截断后的字符串 + 省略号
440
440
  return truncated_text + ellipsis_symbol if truncated_text else ellipsis_symbol
441
+
442
+
443
+ # 字体描边函数
444
+ def draw_text_with_outline(draw, position, text, font, anchor, fill):
445
+ for dx in [-1, 0, 1]:
446
+ for dy in [-1, 0, 1]:
447
+ if dx != 0 or dy != 0:
448
+ draw.text(
449
+ (position[0] + dx, position[1] + dy),
450
+ text,
451
+ font=font,
452
+ anchor=anchor,
453
+ fill=(0, 0, 0, 255),
454
+ )
455
+ draw.text(position, text, font=font, anchor=anchor, fill=fill)
@@ -33,7 +33,9 @@ def split_msg():
33
33
  state["query"] = []
34
34
  state["target"] = None
35
35
  state["is_lazer"] = True if not user_data else user_data.lazer_mode
36
- arg = arg.extract_plain_text().strip()
36
+ arg = (
37
+ arg.extract_plain_text().strip().replace("=", "=").replace(":", ":").replace("&", "&").replace("#", "#")
38
+ )
37
39
  matches = re.findall(pattern, arg)
38
40
  for match in matches:
39
41
  if match[0]:
@@ -85,9 +87,9 @@ def split_msg():
85
87
  if state["source"] == "ppysb" and not arg.strip():
86
88
  sb_user_data = await SbUserData.get_or_none(user_id=qq)
87
89
  if sb_user_data:
88
- state["user"] = sb_user_data.osu_id if user_data else 0
89
- state["username"] = sb_user_data.osu_name if user_data else ""
90
+ state["user"] = sb_user_data.osu_id
91
+ state["username"] = sb_user_data.osu_name
90
92
  else:
91
- state["error"] = "该账号尚未绑定sb 服务器,请输入 /sbbind 用户名 绑定账号"
93
+ state["error"] = "该账号尚未绑定 sb 服务器,请输入 /sbbind 用户名 绑定账号"
92
94
 
93
95
  return Depends(dependency)
Binary file
Binary file
Binary file
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: nonebot-plugin-osubot
3
- Version: 6.20.0
3
+ Version: 6.21.0
4
4
  Summary: OSUbot in NoneBot2
5
5
  License: AGPL-3.0
6
6
  Author-email: yaowan233 <572473053@qq.com>
@@ -1,5 +1,5 @@
1
1
  nonebot_plugin_osubot/__init__.py,sha256=Q-mTTnOIdKiKG6JrVm-kqpPrAhOP9lWyiKHNRqA7gpc,1478
2
- nonebot_plugin_osubot/api.py,sha256=hBG_Ugmkwwdw99XNO--1Q8bzfibxbcH3aZHBbfOkEvc,16304
2
+ nonebot_plugin_osubot/api.py,sha256=8GXbumU7_1D0gyYuGtjHgNT82rSXKZUV-WG5mpUubYE,16336
3
3
  nonebot_plugin_osubot/beatmap_stats_moder.py,sha256=mNNTufc-gvO4NdYa3TnealSZI4-LBoiTlb599SeLBck,2915
4
4
  nonebot_plugin_osubot/config.py,sha256=Ub2s5Ny09-d1ZwT6x8cirB6zWy0brtO-oZV3W0qEM5Q,311
5
5
  nonebot_plugin_osubot/database/__init__.py,sha256=7CDo9xU_DGLQ6uTj_mU_Px92phg_DMU5mP6WvgOxFLY,101
@@ -15,23 +15,23 @@ nonebot_plugin_osubot/draw/catch_preview_templates/js/beatmap/point.js,sha256=AX
15
15
  nonebot_plugin_osubot/draw/catch_preview_templates/js/beatmap/scroll.js,sha256=wR9dkpKabP1TyMssfs8i2WLUxyR_ZX7RRc7KLXUjoXo,1833
16
16
  nonebot_plugin_osubot/draw/catch_preview_templates/js/beatmap/timingpoint.js,sha256=boHMAQOT-860VOi-jQIrq4cwKgCo1kZvNDy0bzUxZLE,803
17
17
  nonebot_plugin_osubot/draw/catch_preview_templates/js/catch/LegacyRandom.js,sha256=HDiks3FCVQBtVK5VzN7YV9Ekkk_WHYi0fPk3-XCQaj8,2155
18
- nonebot_plugin_osubot/draw/catch_preview_templates/js/catch/PalpableCatchHitObject.js,sha256=VVwNgWBtN4FulJwyhqiSrO5HO6x3ENV9sJ7hWmj6lNo,5003
19
- nonebot_plugin_osubot/draw/catch_preview_templates/js/catch/bananashower.js,sha256=BREF62ns4usHR78z9ME1QJHKSX98WLKDv3DI0fJmUsI,922
20
- nonebot_plugin_osubot/draw/catch_preview_templates/js/catch/catch.js,sha256=Nu524ydHXVrKTtQ5VjSPU-EfZJ4xWhsB0SX0HzPeru8,32508
18
+ nonebot_plugin_osubot/draw/catch_preview_templates/js/catch/PalpableCatchHitObject.js,sha256=CnUBiIkAqFUaCrc5TXRtL_fuK20NeK1yMti1Sp4dJAE,5702
19
+ nonebot_plugin_osubot/draw/catch_preview_templates/js/catch/bananashower.js,sha256=sPISMifUWVXK9nXuOD7JUPeXBNPBKquiDNKpYzbSJyg,988
20
+ nonebot_plugin_osubot/draw/catch_preview_templates/js/catch/catch.js,sha256=uLbMrZBZfvj1YxJVIeirFWVVJ2apLPfB1VY_KHq-hSE,34561
21
21
  nonebot_plugin_osubot/draw/catch_preview_templates/js/catch/fruit.js,sha256=ABvFMbmXP0t5wACUhSVbu8sVJ845mWJ9sWiQJtMV5co,437
22
- nonebot_plugin_osubot/draw/catch_preview_templates/js/catch/juicestream.js,sha256=_42Cl2DirQOOwlSRrJYwk5pKVrqs4dX6Z9M3i2jYvp8,7170
22
+ nonebot_plugin_osubot/draw/catch_preview_templates/js/catch/juicestream.js,sha256=kWjmPtT_UgnUlg5G-_8q-1AyNTv6GLRp0bOOfiMcojc,7347
23
23
  nonebot_plugin_osubot/draw/catch_preview_templates/js/fakeaudio.js,sha256=gJrdVwNrAVjWx80z_XkzybL9ldfV97nKl7k1v7NulBg,1211
24
24
  nonebot_plugin_osubot/draw/catch_preview_templates/js/preview.js,sha256=5zKNenhzhMdP_SPuA74wbtgsqiqUNL8PIJ4OYFW2r-U,2875
25
25
  nonebot_plugin_osubot/draw/catch_preview_templates/js/standard/curve/bezier2.js,sha256=J3wvdScuhYCngTvZ3vg9ld6yoDE09ht5g4Etsp4bJkE,955
26
26
  nonebot_plugin_osubot/draw/catch_preview_templates/js/standard/curve/catmullcurve.js,sha256=aGmfIAONuOoy87W5Pyl1X8ifr3w6W9mkM_AENmZRUF0,932
27
27
  nonebot_plugin_osubot/draw/catch_preview_templates/js/standard/curve/centripetalcatmullrom.js,sha256=bndps8pDX4JMEbqLxIVb-aO7IoHHXnT8-jkWJnLlfq0,1203
28
28
  nonebot_plugin_osubot/draw/catch_preview_templates/js/standard/curve/circumstancedcircle.js,sha256=qf7wZMH3-thilEoB1Grrhr9W-U-JIClRNkhZh2RzGTY,1558
29
- nonebot_plugin_osubot/draw/catch_preview_templates/js/standard/curve/curve.js,sha256=mWs8BuYnMoSFYnbqz7UZtC4UfHNu86x8BEF0oVQomMo,646
29
+ nonebot_plugin_osubot/draw/catch_preview_templates/js/standard/curve/curve.js,sha256=KeR8szJ0N2FWBTYMnXBcnQFWYHxqRRh0f-oaALSXvYs,1002
30
30
  nonebot_plugin_osubot/draw/catch_preview_templates/js/standard/curve/curvetype.js,sha256=y3uiQSz1BjGIhIMn4fNlplPRGqxozHOuIa8jN-G06Vs,496
31
31
  nonebot_plugin_osubot/draw/catch_preview_templates/js/standard/curve/equaldistancemulticurve.js,sha256=U8sjoBG1ETEa9vlToqc32hyQYj41KrcRVvEdxnD-uGo,2280
32
32
  nonebot_plugin_osubot/draw/catch_preview_templates/js/standard/curve/linearbezier.js,sha256=sGe0u0Aqwh8R4y2BP91Dsz1MfhvtvTrrOVMV_gYsQhY,1075
33
33
  nonebot_plugin_osubot/draw/catch_preview_templates/js/standard/hitcircle.js,sha256=erJCRQYojmlCnTvnkrWMJHkJxBabeWopE8PACr1aSUw,2391
34
- nonebot_plugin_osubot/draw/catch_preview_templates/js/standard/slider.js,sha256=aw8U-JgFrl29r2_5l4gW7FlSJ4QdcIBNTWSuiAZTnp4,3831
34
+ nonebot_plugin_osubot/draw/catch_preview_templates/js/standard/slider.js,sha256=alosJJRDgXreSrheROxwVi4Dt9S3lxq9aZfG_EQCbWU,3873
35
35
  nonebot_plugin_osubot/draw/catch_preview_templates/js/standard/spinner.js,sha256=9El2G7UfJv1xikHmfO5-VJJ7e6f-1tOPLyrMf2huh7g,1668
36
36
  nonebot_plugin_osubot/draw/catch_preview_templates/js/util.js,sha256=XveUlX-d0vrUnXaGbR8y428s6Nw2zhDR235pFko_MxM,1504
37
37
  nonebot_plugin_osubot/draw/catch_preview_templates/js/viewbox.js,sha256=RPsUyoKNAQub2n7oUGwWyFppZu0iVuy6DD5gggQZS3E,1641
@@ -42,14 +42,14 @@ nonebot_plugin_osubot/draw/info.py,sha256=i2YcJmSdTpwhZl_nDe7GJv4jQTB8_9oBfpq2Zw
42
42
  nonebot_plugin_osubot/draw/map.py,sha256=4M8xRd0dIbC5g1s8I4eTZ3vRglM6r_TSznFOZ62K2wk,8654
43
43
  nonebot_plugin_osubot/draw/match_history.py,sha256=GBJl6lAA27U7NSMC2isEzD_YsoIPAeG6ijDu7Oflcl0,997
44
44
  nonebot_plugin_osubot/draw/rating.py,sha256=pA7mGLI4IujmYB6kQf_tSkR7mZGpUAVLRLyaAsZhqTM,20397
45
- nonebot_plugin_osubot/draw/score.py,sha256=Y2-BUuKYgYoXk-Uq0PFNg3WaU_SUfSB6Ebo446xslUg,27553
46
- nonebot_plugin_osubot/draw/static.py,sha256=2rznsXZTcKggQ5JSpsJg4y6uWP4e-Y40U7v_QAwLT4Q,3969
45
+ nonebot_plugin_osubot/draw/score.py,sha256=izYS28IovyFXACLkyQL9Qtp8fUrguoO63xKdawZv70Y,29414
46
+ nonebot_plugin_osubot/draw/static.py,sha256=28_ta4ivORxdX_-P7OdOp1VzJK_Tw0-VudfJOY9kW8I,4059
47
47
  nonebot_plugin_osubot/draw/taiko_preview.py,sha256=tqhuHSdxUJEuXqKHMJDeSLdusuJhSnMMiaG5FbUnaJw,11441
48
48
  nonebot_plugin_osubot/draw/templates/bpa_chart.html,sha256=cnpM0qRvvyCMTRP-mIOABQlaaqxQwG5kLUxlo4h7F7w,7012
49
49
  nonebot_plugin_osubot/draw/templates/echarts.min.js,sha256=IF32ooP8NPIzQg_fs7lVHpwG92JcCPE1TZAEyFSgGZU,1022939
50
50
  nonebot_plugin_osubot/draw/templates/mod_chart.html,sha256=Iz71KM5v9z_Rt2vqJ5WIZumRIHgDfcGIUmWGvVGZ-nQ,1508
51
51
  nonebot_plugin_osubot/draw/templates/pp_rank_line_chart.html,sha256=Gyf-GR8ZBlWQTks0TlB3M8EWUBMVwiUaesFAmDISxLo,1417
52
- nonebot_plugin_osubot/draw/utils.py,sha256=LZVDmrQizVwVFX0kDJISYYO68jmZbn5Xz40gV5bj8bo,15276
52
+ nonebot_plugin_osubot/draw/utils.py,sha256=r-It_mt4o3ckkjanAynJ28ICGxTYk3o2XMnAH__0rUQ,15764
53
53
  nonebot_plugin_osubot/exceptions.py,sha256=N_FsEk-9Eu2QnuznhdfWn6OoyA1HH73Q7bUaY89gVi0,40
54
54
  nonebot_plugin_osubot/file.py,sha256=GhG54EdVsxFf8_8GZOPh4YGvw9iw5bAX9JFz4Mg4kMg,4379
55
55
  nonebot_plugin_osubot/info/__init__.py,sha256=I7YlMQiuHExEeJWqyzZb2I-Vl2uql3Py2LdhSH2Z9N0,136
@@ -80,7 +80,7 @@ nonebot_plugin_osubot/matcher/score.py,sha256=Nk6dpDlszKJKdboTSQRBf-wMGioHIPqKSV
80
80
  nonebot_plugin_osubot/matcher/update.py,sha256=MHpxoJmU0hKW82XuM9YpyCxUUjjiNKwejnRgYwueR4Q,3168
81
81
  nonebot_plugin_osubot/matcher/update_mode.py,sha256=0Wy6Y1-rN7XcqBZyo37mYFdixq-4HxCwZftUaiYhZqE,1602
82
82
  nonebot_plugin_osubot/matcher/url_match.py,sha256=opx4DYSQ83tk0qlMOBirl4TC49WXHgMZ04lMr9NCHNs,746
83
- nonebot_plugin_osubot/matcher/utils.py,sha256=BUAorxXrWioiMzR0nZ8NUeaV48ZGUDe4FXLWxKT72g4,4240
83
+ nonebot_plugin_osubot/matcher/utils.py,sha256=gWmNa31wUxcY_PNSNLy348x5_7sTY9ttMKH-5V5jkuE,4304
84
84
  nonebot_plugin_osubot/mods.py,sha256=vxIWYX0HwTxetPAHWZK5ojEMfqV9HFlWT0YC4Yncgb8,1402
85
85
  nonebot_plugin_osubot/network/__init__.py,sha256=WOijcd81yhnpGKYeiDIOxbBDVI12GHPRGoOFfxrUuQk,61
86
86
  nonebot_plugin_osubot/network/auto_retry.py,sha256=vDfYGbEVPF6WZLYXmRVkNvaxf6_6RyIqEAcA7TRwV_k,565
@@ -372,10 +372,10 @@ nonebot_plugin_osubot/osufile/mods/SD.png,sha256=JptCZkXv8kOpIksYimWdC7hRJXShL7N
372
372
  nonebot_plugin_osubot/osufile/mods/SO.png,sha256=qKPoP-LXNU3nSVn8e7z2d6LddFOG-v4gbBEUOb6BtXU,1493
373
373
  nonebot_plugin_osubot/osufile/mods/TD.png,sha256=ZAprUMXNcqs_tXPF0HU34qa_NUZo6U-NemMjVxBOk10,1309
374
374
  nonebot_plugin_osubot/osufile/mods/V2.png,sha256=Y3ju7KMECnGiECRM1MnZUuuwQqbflkdz6I7XjCCTw2E,1354
375
- nonebot_plugin_osubot/osufile/pfm_ctb.png,sha256=UYiDqal_wQHJxqOGYrXNs587FZg6qGALAA6OaQaqPws,44488
376
- nonebot_plugin_osubot/osufile/pfm_mania.png,sha256=YnLEs9SUivR8BfTZQDyGZXQJMYLIB_v8KuA9qbuysMQ,45512
377
- nonebot_plugin_osubot/osufile/pfm_std.png,sha256=_H7i_QKzwkDH70avZLvn7dqcldRpKBHH_Y8AkflmNTI,47809
378
- nonebot_plugin_osubot/osufile/pfm_taiko.png,sha256=ds3C71ZKGiXd6LgxuLfrKxDrfPbzrbMddmh9IwLW3tk,44271
375
+ nonebot_plugin_osubot/osufile/pfm_ctb.png,sha256=9JJu8J1gR_nQhb1EWOuJUOjNitYK_gcH6MRGiYtvsGI,53451
376
+ nonebot_plugin_osubot/osufile/pfm_mania.png,sha256=PKlAezcKALo0UMnrYvTCL3pd-oJNEU76qLVPhfDLFdk,52208
377
+ nonebot_plugin_osubot/osufile/pfm_std.png,sha256=ts7e7IUeoalLuw0Pww0X3trXUpZwnWRsOqdQ1RzcdFM,57032
378
+ nonebot_plugin_osubot/osufile/pfm_taiko.png,sha256=7OAhXVfL_j7pZ4MZAXexgSMNmUyVNsvvq1Ij2XzFEgc,55756
379
379
  nonebot_plugin_osubot/osufile/ranking/ranking-A.png,sha256=nA4kI67IbnC-gfu1pCnPupHC45Uoqa6LYmJKawzV_L0,4364
380
380
  nonebot_plugin_osubot/osufile/ranking/ranking-B.png,sha256=KJCEGr7OkQ12rqng6_JL9r6qLTAclPO_PtHHmLFQooY,4214
381
381
  nonebot_plugin_osubot/osufile/ranking/ranking-C.png,sha256=SsIWOTOymgMze521v4G_rdqL6QqpE64R1dhxpmhzBAc,3862
@@ -403,6 +403,6 @@ nonebot_plugin_osubot/schema/sayo_beatmap.py,sha256=lS1PQZ-HvHl0VhkzlI0-pNLeJrLY
403
403
  nonebot_plugin_osubot/schema/score.py,sha256=zHU-w2e7RzMDL8vdPkX5vggcqalBo83JTvu96abcflo,3124
404
404
  nonebot_plugin_osubot/schema/user.py,sha256=sxNmqymG_kIVuGuzfchSv9UML6NPG70cqo2_h5xDIpM,2250
405
405
  nonebot_plugin_osubot/utils/__init__.py,sha256=pyv8XxBcCOeQVDj1E4dgvktzcefgQXfKBlarsYGx1sg,815
406
- nonebot_plugin_osubot-6.20.0.dist-info/WHEEL,sha256=B19PGBCYhWaz2p_UjAoRVh767nYQfk14Sn4TpIZ-nfU,87
407
- nonebot_plugin_osubot-6.20.0.dist-info/METADATA,sha256=h46-68Jkbr_-bhXPbyK_BS-f7kVOO585n8TGDpPYijc,4476
408
- nonebot_plugin_osubot-6.20.0.dist-info/RECORD,,
406
+ nonebot_plugin_osubot-6.21.0.dist-info/WHEEL,sha256=B19PGBCYhWaz2p_UjAoRVh767nYQfk14Sn4TpIZ-nfU,87
407
+ nonebot_plugin_osubot-6.21.0.dist-info/METADATA,sha256=uKS_5rmuhM8-4dD90UGo6kb-p5n90veBEKDQR6aDB5Y,4476
408
+ nonebot_plugin_osubot-6.21.0.dist-info/RECORD,,